for-select 裡的 time.After 每圈重置

case <-time.After(2*time.Second) 寫在 for 迴圈裡的 select,語意不是「總共只等 2 秒」,而是「連續 2 秒沒有新資料」才逾時——因為每繞一圈,那行 time.After 就被重新求值一次,鬧鐘重新開始。

start := time.Now()
ch := make(chan string)
 
go func() {                       // 每 1 秒送一筆,共 4 筆
    for i := 1; i <= 4; i++ {
        time.Sleep(1 * time.Second)
        ch <- fmt.Sprintf("資料 %d", i)
    }
}()
 
for {
    select {
    case msg := <-ch:
        fmt.Printf("[%.0fs] 收到 %s\n", time.Since(start).Seconds(), msg)
    case <-time.After(2 * time.Second):   // ← 每圈都重新計時
        fmt.Printf("[%.0fs] 逾時\n", time.Since(start).Seconds())
        return
    }
}
[1s] 收到 資料 1
[2s] 收到 資料 2
[3s] 收到 資料 3
[4s] 收到 資料 4
[6s] 逾時

跑了 6 秒才逾時,不是 2 秒。 每收到一筆資料就重開一圈、鬧鐘歸零,最後一筆在第 4 秒,再等滿 2 秒才觸發。

兩種語意,寫法不同

// idle timeout:連續 N 秒沒動靜才放棄
for {
    select {
    case msg := <-ch: ...
    case <-time.After(2 * time.Second): return
    }
}
 
// 總時限:整段工作最多 N 秒
timeout := time.After(2 * time.Second)   // ← 拉到迴圈外,只求值一次
for {
    select {
    case msg := <-ch: ...
    case <-timeout: return
    }
}

差別只在那行寫在迴圈裡還是外面。兩種都對,但要知道自己要的是哪一種——大部分人以為自己寫的是總時限,其實寫成了 idle timeout。

另外,被丟掉的那些 timer 不會馬上釋放(要等它自己到期),高頻迴圈裡用 time.After 會累積垃圾,那種場合該改用 time.NewTimer 並自己 Reset / Stop

相關:select 的 case 後面不是條件而是一個 channelselect 用 default 做非阻塞,用 time.After 做逾時