select 阻塞的是呼叫它的 goroutine,不是整個程式

for 裡的 select 確實是同步的:這一圈選到並執行完,才會進下一圈。但要分清楚——「main 在等」不等於「整個程式在等」

go func() { time.Sleep(1 * time.Second); a <- "A" }()   // 背景同時在跑
go func() { time.Sleep(2 * time.Second); b <- "B" }()
 
for i := 0; i < 2; i++ {
    select {
    case msg := <-a: fmt.Println(msg)
    case msg := <-b: fmt.Println(msg)
    }
}
時間軸        0s -------- 1s -------- 2s
main:         [ select 等... ] [ select 等... ]
goroutine A:  [ 睡 1 秒 ] → 丟資料
goroutine B:  [ 睡 2 秒 ............] → 丟資料

A 睡 1 秒 + B 睡 2 秒,總共只花 2 秒不是 3 秒。 兩條 goroutine 從頭到尾都在同時推進,被卡住的只有 main 這一條。

所以 select 的定位是:讓一條執行流去「收割」多條平行工作的結果。它本身是同步的(會卡住呼叫它的那條 goroutine),但它等待的對象是非同步的。

這也是為什麼 select 幾乎總是寫在 for 裡——一次 select 只處理一個事件,要持續收割就得繞回來再等一次。

相關:Goroutine 是用 go 關鍵字啟動的輕量任務select 等不到條件時會 deadlock 或 goroutine leak