在執行select語句時,運行時系統會自上而下判斷每個case中的發送或接收操作是否可以被立即執行,這里的立即執行的意思是當前goroutine不會因此操作而阻塞。

從左往右,從上往下
對于select的求值,一條case中,從左往右求值;多條case,從上往下,下面舉幾個例子說明:
var ch2 chan int
var ch4 chan int
var chs = []chan int{ch2, ch4}
var numbers = []int{1, 2, 3, 4, 5}
func main() {
select {
case getChan(0) <- getNumber(2):
fmt.Println("1th case is selected")
case getChan(1) <- getNumber(3):
fmt.Println("2th case is selected")
default:
fmt.Println("default!")
}
}
func getChan(i int) chan int {
fmt.Printf("chs[%d]\n",i);
return chs[i]
}
func getNumber(i int) int {
fmt.Printf("numbers[%d]\n",i)
return numbers[i]
}
代碼分析
這段代碼設計的也比較巧妙,調試看出getChan先于getNumber執行,說明一條case中是從左往右求值的;同理,多條case是從上往下執行。
但是有一個問題,我們發現ch2和ch4都是nil,簡化形式如下:
//ch == nil
var ch chan int
select {
case ch <- 0:
default:
fmt.Println("默認執行...")
}
select的case中允許這樣寫,但相當于沒有寫,永遠不會執行。除此外,select中還有很多奇怪的使用方式,再比如:
//無緩沖通道
ch := make(chan int)
select {
case ch <- 0:
default:
fmt.Println("默認執行...")
}
沒有接收方代碼,但是依然也不會錯。
具體運行規則
一、在執行select語句時,運行時系統會自上而下判斷每個case中的發送或接收操作是否可以被立即執行,這里的立即執行的意思是當前goroutine不會因此操作而阻塞。
要點:case中的語句要能立即執行
二、當發現第一個滿足條件的case時,運行時系統就會執行該case所包含的語句,同時,其它case也會被忽略。
要點:只執行一個case
三、如果同時有多個case滿足條件,那么運行時會通過一個偽隨機的算法決定哪一個case將會執行。
要點:多個case同時滿足,使用算法選擇一個
chanCap := 5
ch := make(chan int, chanCap)
for i := 0; i < chanCap; i++ {
select {
case ch <- 1:
case ch <- 2:
case ch <- 3:
}
}
for i := 0; i < chanCap; i++ {
fmt.Printf("%v ", <-ch)
}
代碼分析
這段代碼也很巧妙,驗證了多個case同時滿足條件時,如何進行隨機選擇。
輸出:
3 2 2 2 3
2 1 1 1 2
1 1 3 2 1
可以看出,作者電腦上每次顯示的都是一些隨機選擇。
四、如果所有的case都不能立即執行,且沒有default,那么select會阻塞,直到某個接收或發送的case操作能立即執行。
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(1 * time.Second)
ch1 <- 10
}()
go func() {
time.Sleep(1 * time.Second)
ch1 <- 20
}()
select {
case d := <-ch1:
fmt.Println(d)
case d := <-ch2:
fmt.Println(d)
}
代碼分析
select等待兩個channel,由于都延時且沒有default,所以select阻塞等待。1s后ch1可以立即執行,因此打印10,select也退出選擇。
五、如果所有通道都是nil且沒有default,且會發生死鎖
// ch1 == nil
var ch1 chan int
select {
case <-ch1:
fmt.Println("hello")
// 沒有default
}
select和for的配合
select和for的搭配有很多問題,本篇只說明一個問題:

select的case中可以使用break,但是只跳出select(區域1),如果想跳出for循環(區域2),需要一些輔助技巧。
總結
介紹了select的一些基本選擇規則,也是需要背誦的知識點。