本文基於 Go 1.14
select 允許在一個 goroutine 中管理多個 channel。但是,當所有 channel 同時就緒的時候,go 需要在其中選擇一個執行。此外,go 還需要處理沒有 channel 就緒的情況,我們先從就緒的 channel 開始。
順序select 不會按照任何規則或者優先級選擇就緒的 channel。go 標準庫在每次執行的時候,都會將他們順序打亂,也就是說不能保證任何順序。
看一個有三個就緒的 channel 的例子:
func main() {
a := make(chan bool, 100)
b := make(chan bool, 100)
c := make(chan bool, 100)
for i := 0; i < 10; i++ {
a <- true
b <- true
c <- true
}
for i := 0; i < 10; i++ {
select {
case <-a:
print("< a")
case <-b:
print("< b")
case <-c:
print("< c")
default:
print("< default")
}
}
}這三個 channel 的緩衝區都填滿了,使得 select 選擇時不會堵塞。下面是程序的輸出:
< b< a< a< b< c< c< c< a< b< b在 select 的每次迭代中,case 都會被打亂:
由於 go 不會刪除重複的 channel,所以可以使用多次添加 case 來影響結果,代碼如下:
func main() {
a := make(chan bool, 100)
b := make(chan bool, 100)
c := make(chan bool, 100)
for i := 0; i < 10; i++ {
a <- true
b <- true
c <- true
}
for i := 0; i < 10; i++ {
select {
case <-a:
print("< a")
case <-a:
print("< a")
case <-a:
print("< a")
case <-a:
print("< a")
case <-a:
print("< a")
case <-a:
print("< a")
case <-a:
print("< a")
case <-b:
print("< b")
case <-c:
print("< c")
default:
print("< default")
}
}
}輸出的結果:
< c< a< b< a< b< a< a< c< a< a當所有 channel 同時準備就緒時,有 80%的機會選擇通道 a。下面來看一下 channel 未就緒的情況。
沒有就緒 channelsselect 運行時,如果沒有一個 case channel 就緒,那麼他就會運行 default:,如果 select 中沒有寫 default,那麼他就進入等待狀態,如下面這個例子
func main() {
a := make(chan bool, 100)
b := make(chan bool, 100)
Go func() {
time.Sleep(time.Minute)
for i := 0; i < 10; i++ {
a <- true
b <- true
}
}()
for i := 0; i < 10; i++ {
select {
case <-a:
print("< a")
case <-b:
print("< b")
}
}
}上面那個例子中,將在一分鐘後列印結果。select 阻塞在 channel 上。這種情況下,處理 select 的函數將會訂閱所有 channel 並且等待,下面是一個 goroutine#7 在 select 中等待的示例,其中另一個 goroutine#4 也在等待 channel:
Goroutine(G7)訂閱所有頻道並在列表末尾等待。如果 channel 發送了一條消息,channel 將通知已在等待該消息的另一個 Goroutine。一旦收到通知,select 將取消訂閱所有 channel,並且返回到代碼運行.
更多關於 channel 與等待隊列的信息,請查看作者另外一篇文章*Go: 帶緩衝和不帶緩衝的 Channels*[1]。
上面介紹的邏輯,都是針對於有兩個或者以上的活動的 channel,實際上如果只有一個活動的 channel,Go 樂意簡化 select。
簡化如果只有一個 case 加上一個 default,例子:
func main() {
t:= time.NewTicker(time.Second)
for {
select {
case <-t.C:
print("1 second ")
default:
print("default branch")
}
}
}這種情況下。Go 會以非阻塞模式讀取 channel 的操作替換 select 語句。如果 channel 在緩衝區中沒有任何值,或者發送方準備發送消息,將會運行 default。就像下面這張圖:
如果沒有 default,則 Go 通過阻塞 channel 的操作方式重寫 select 語句。
via: https://medium.com/a-journey-with-go/go-ordering-in-select-statements-fd0ff80fd8d6
作者:Vincent Blanchon[2]譯者:yixiao9206[3]校對:polaris1119[4]
本文由 GCTT[5] 原創編譯,Go 中文網[6] 榮譽推出
參考資料[1]Go: 帶緩衝和不帶緩衝的 Channels: https://studygolang.com/articles/23538
[2]Vincent Blanchon: https://medium.com/@blanchon.vincent
[3]yixiao9206: https://github.com/yixiao9206
[4]polaris1119: https://github.com/polaris1119
[5]GCTT: https://github.com/studygolang/GCTT
[6]Go 中文網: https://studygolang.com/