select 分三階段:求值全部、等待全部、只執行一個
「select 會把所有 case 都跑一遍嗎?」——對一半。要拆成三個階段看:
| 階段 | 做什麼 | 涵蓋哪些 case |
|---|---|---|
| ① 求值 | 算出每個 case 要等的 channel 是誰 | 全部,由上到下各算一次 |
| ② 等待 | 同時盯著這些 channel | 全部 |
| ③ 執行 | 跑 case 大括號裡的程式碼 | 只有贏的那一個 |
func makeChan(name string, delay time.Duration) <-chan string {
fmt.Println(" 求值階段:呼叫了 makeChan(" + name + ")")
ch := make(chan string)
go func() { time.Sleep(delay); ch <- name }()
return ch
}
select {
case msg := <-makeChan("A", 3*time.Second): fmt.Println("執行階段:A", msg)
case msg := <-makeChan("B", 1*time.Second): fmt.Println("執行階段:B", msg)
case msg := <-makeChan("C", 2*time.Second): fmt.Println("執行階段:C", msg)
} 求值階段:呼叫了 makeChan(A)
求值階段:呼叫了 makeChan(B)
求值階段:呼叫了 makeChan(C)
執行階段:B B三個 makeChan 都被呼叫了(含它們各自開的 goroutine),但只有最快的 B 的 body 被執行。A 與 C 的 goroutine 仍在背景睡,時間到會送資料卻沒人接,那些資料就浪費掉了。
副作用是實際的:case 的 channel 運算式若有副作用(開連線、發請求、起 goroutine),每次跑到這個 select 都會全部發生一次,不是只發生在被選中的那條。
用詞修正
channel 不是「回傳」結果,而是**「有沒有資料可以拿」**。select 等的是「可讀 / 可寫」這件事,不是函式回傳值。
相關:select 的 case 後面不是條件而是一個 channel、select 是 channel 版的 switch