Go / 並行回顧

GOROUTINE · CHANNEL · CONTEXT

接收者離開後,
誰還在等待?

只需要第一個結果,卻啟動了會送出三個值的 producer。第一個值取到了,後面的 send 怎麼結束?

一次沒完成的傳送

  • producer 送出 2consumer 接收;無緩衝 channel 的這次傳送完成。
  • consumer 返回它已取得結果,不再接收。
  • producer 嘗試送出 4沒有接收者,send 阻塞,goroutine 無法返回。

假設外層程式仍在運行。接收函式返回,不會替另一個 goroutine 執行 return。

讓等待有另一個出口

兩個版本都取第一個值。修正版使用標準庫 context。

只有 send:等待接收者

func first() int {
  out := make(chan int)
  go func() {
    defer close(out)
    for _, v := range []int{2, 4, 6} {
      out <- v
    }
  }()
  return <-out
}

defer close(out) 要等 goroutine 返回才會執行。它無法解除前面卡住的 send。

select:也能收到取消通知

func first() int {
  ctx, cancel := context.WithCancel(
    context.Background(),
  )
  defer cancel()
  out := make(chan int)
  go func() {
    defer close(out)
    for _, v := range []int{2, 4, 6} {
      select {
      case out <- v:
      case <-ctx.Done():
        return
      }
    }
  }()
  return <-out
}

first 返回時呼叫 cancel。producer 可從 Done 分支返回,再關閉輸出。

通知、關閉、等待,各有責任

  • consumer 呼叫 cancel表示不再需要結果。cancel 不會強制終止 goroutine,也不等待它結束。
  • 唯一 producer 關閉 out只有它知道自己不會再送值。consumer 不應用 close 中止 sender;向已關閉 channel 傳送會 panic。
  • 工作自行處理取消select 才是退出路徑。換成不檢查 Done 的運算或阻塞 I/O,cancel 無法替它中斷。

若其他程式碼需要確認 producer 已退出,另用 WaitGroup 等待。若 send 與 Done 同時就緒,select 沒有「取消優先」保證。

Go 官方依據

  1. Pipelines and cancellationgo.dev/blog/pipelines — Stopping short、Explicit cancellation。
  2. context:WithCancel、CancelFuncpkg.go.dev/context — 取消通知與工作結束是兩回事。
  3. 語言規格:Send、Select、Closego.dev/ref/spec — 傳送的阻塞條件、就緒分支選擇及關閉行為。