select的作用
Go里面提供了一個關鍵字 select
, 通過 select
可以監聽channel上的數據流動.
select
的用法與 switch
語言非常類似, 由 select
開始一個新的選擇塊, 每個選擇條件由 case
語句來描述.
與 switch
語句相比, select
有比較多的限制, 其中最大的一條限制就是每個case語句里必須是一個IO操作.
大致的結構如下:
select {
case <- chan1:
// 如果chan1成功讀到數據, 則進行該case處理語句
case chan2 <- -1:
// 如果成功向chan2寫入數據, 則進行該case處理語句
default:
// 如果上面都沒有成功, 則進入default處理流程
}
在一個 select
語句中, Go語言會按照順序從頭至尾評估每一個發送和接收的語句.
如果其中的任意一條語句可以繼續執行(即沒有阻塞), 那么就從那些可以執行的語句中任意選擇一條來使用.
如果沒有任意一條語句可以執行(即所有的通道都被阻塞), 那么有兩種可能的情況:
- 如果給出了default語句, 那么就會執行default語句, 同時程序的執行會從select語句后的語句中恢復.
- 如果沒有default語句, 那么select語句將被阻塞, 直到至少有一個通信可以進行下去.
select的基本使用
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
ch := make(chan int) // 用來進行數據通信的channel
quit := make(chan bool) // 用來判斷是否退出的channel
go func() { // 寫數據
for i:=0; i < 5; i++ {
ch <- i
time.Sleep(time.Second)
}
close(ch)
quit <- true // 通知主go程 退出
runtime.Goexit()
}()
for {
select {
case num := <- ch:
fmt.Println("讀到: ", num)
case <- quit:
return
//break // break 跳出select循環
}
fmt.Println("============")
}
}
結果:
讀到: 0
============
讀到: 1
============
讀到: 2
============
讀到: 3
============
讀到: 4
============
讀到: 0
============
讀到: 0
============
注意, 因為是任意挑選一個case執行, 所以最后的 讀到:0 的數量相當於是個隨機數.
所以, 總結下select的注意事項:
- case后面必須是IO操作, 不可以是判別表達式.
- 監聽的case中, 沒有滿足監聽條件, 阻塞.
- 監聽的case中, 有多個滿足監聽條件, 任選一個執行.
- 可以使用default來處理所有case都不滿足監聽條件的狀況.(通常不用, 會產生 忙輪詢)
- select自身不帶有循環機制, 需借助外層for循環來進行循環監聽
- break只能跳出select. 類似於switch中的用法.
select實現斐波那契數列
package main
import (
"fmt"
"runtime"
)
func fibonacci(ch <-chan int, quit <-chan bool) {
for {
select {
case num := <-ch:
fmt.Println(num)
case <-quit:
//return
runtime.Goexit()
}
}
}
func main() {
ch := make(chan int)
quit := make(chan bool)
go fibonacci(ch, quit)
x, y := 1, 1
for i := 0; i < 50; i++ {
ch <- x
x, y = y, x+y
}
quit <- true
}
為什么要用到select?
如果不用select的話, 每一個case都要創建一個go程去處理, 這樣的話太浪費了, 而用select的話, 只需要一個go程就可以了.
超時
有時候會出現goroutine阻塞的情況, 那么我們如何避免整個程序進入阻塞的情況呢?我們可以利用select來設置超時, 通過如下的方式來實現:
示例代碼:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
timeOut := make(chan bool)
go func() {
for {
select {
case num := <- ch:
fmt.Println("num: ", num)
case <- time.After(5 * time.Second):
fmt.Println("timeout")
timeOut <- true
return
}
}
}()
ch <- 666
<- timeOut // 主go程, 阻塞等待子go程通知, 退出
fmt.Println("finish.")
}
select監聽time.After() 中channel的讀事件, 如果定時時間到, 系統會向該channel中寫入系統當前時間.
李培冠博客
歡迎訪問我的個人網站:
李培冠博客:lpgit.com