4.3 select:同時等待多種事件
select 是 Go 用來同時等待多個 channel 操作的控制結構。它的核心用途是讓一個 goroutine 同時處理資料輸入、取消訊號、timer/ticker 與 fallback 行為。
本章目標
學完本章後,你將能夠:
- 理解
select的基本語法 - 同時等待多個 channel
- 用
ctx.Done()停止事件迴圈 - 用 ticker 建立定時工作
- 理解
default的 non-blocking 行為
【觀察】select 同時等待多個 channel
select 的核心規則是:多個 case 中哪個 channel 先 ready,就執行哪個 case。以下範例同時等待 job 和取消訊號:
1func worker(ctx context.Context, jobs <-chan Job) {
2 for {
3 select {
4 case <-ctx.Done():
5 return
6 case job := <-jobs:
7 handle(job)
8 }
9 }
10}這個 worker 不需要先固定等待 jobs,也不需要用輪詢檢查 context。select 會同時等待兩者。
【判讀】select loop 是長期 goroutine 的生命週期中心
長期 goroutine 的核心規則是:事件迴圈必須同時處理工作來源與退出訊號。只讀工作 channel 而不讀 ctx.Done(),goroutine 可能無法按上層要求停止。
1func worker(ctx context.Context, jobs <-chan Job) {
2 for {
3 select {
4 case <-ctx.Done():
5 return
6 case job, ok := <-jobs:
7 if !ok {
8 return
9 }
10 handle(job)
11 }
12 }
13}ok == false 表示 channel 已關閉。這讓 worker 在「上層取消」和「工作來源結束」兩種情境都能退出。
【策略】ticker case 要有 Stop
ticker 的核心規則是:建立 ticker 後要呼叫 Stop(),避免不再使用時仍保留 runtime 資源。
1func cleanupLoop(ctx context.Context) {
2 ticker := time.NewTicker(time.Minute)
3 defer ticker.Stop()
4
5 for {
6 select {
7 case <-ctx.Done():
8 return
9 case <-ticker.C:
10 cleanup()
11 }
12 }
13}這種模式常用於定期清理、同步、掃描或報表輸出。
【執行】default 建立 non-blocking select
default 的核心規則是:沒有任何 channel ready 時,立即執行 default。這會讓 select 不阻塞。
1select {
2case jobs <- job:
3 return nil
4default:
5 return ErrQueueFull
6}這段程式嘗試把 job 放進 channel;如果 channel 不能立即接收,就回傳 ErrQueueFull。這適合保護呼叫端不要被背景佇列卡住。
不適合用 default 的情境是你其實需要等待結果:
1select {
2case result := <-results:
3 return result
4default:
5 return Result{} // 可能過早返回
6}若結果是必要的,應該等待或設定 timeout,而不是直接 default。
timeout pattern
timeout 的核心規則是:需要等待但不能無限等待時,用 time.After 或 context timeout。
1select {
2case result := <-results:
3 return result, nil
4case <-time.After(2 * time.Second):
5 return Result{}, errors.New("timeout")
6}在較大的系統中,通常更偏好 context.WithTimeout,讓 timeout 可以沿呼叫鏈傳遞。