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 後面不是條件而是一個 channel、select 用 default 做非阻塞,用 time.After 做逾時