channel 是 Go 用來在 goroutine 之間傳遞資料的同步工具。它的核心意義是建立資料流邊界:誰送出資料、誰接收資料、當接收端跟不上時送出端如何被阻擋或丟棄。

本章目標

學完本章後,你將能夠:

  1. 建立與使用 channel
  2. 看懂 channel 的方向與資料型別
  3. 理解 buffered channel 的 backpressure 意義
  4. 分辨 blocking send 與 non-blocking send
  5. 用 channel 畫出資料流

【觀察】channel 連接送出端與接收端

channel 的核心規則是:送出端用 <- 把值放入 channel,接收端用 <- 從 channel 取出值。以下範例建立一個傳遞 string 的 channel:

1messages := make(chan string)
2
3go func() {
4    messages <- "hello"
5}()
6
7msg := <-messages
8fmt.Println(msg)

make(chan string) 建立只能傳 string 的 channel。messages <- "hello" 是送出,msg := <-messages 是接收。

【判讀】channel 是同步點,不只是佇列

unbuffered channel 的核心規則是:送出和接收必須同時準備好,資料才會通過。這表示 channel 也是同步點。

1ch := make(chan int)
2
3go func() {
4    ch <- 1 // 等到有人接收才會繼續
5}()
6
7value := <-ch
8fmt.Println(value)

buffered channel 的核心規則是:buffer 未滿時送出不會阻塞,buffer 滿時送出會阻塞。

1jobs := make(chan Job, 10)
2jobs <- Job{ID: "1"}

buffer 大小不是隨便的數字。它代表系統允許累積多少尚未處理的工作;接收端處理速度跟不上時,buffer 會逐漸填滿,最後形成 backpressure 。

【策略】用方向限制表達所有權

channel direction 的核心規則是:函式簽名應限制自己只需要的能力。Go 可以用 channel direction 表達函式只讀或只寫:

1func producer(out chan<- Job) {
2    out <- Job{ID: "1"}
3}
4
5func consumer(in <-chan Job) {
6    job := <-in
7    handle(job)
8}

chan<- Job 表示只能送出,<-chan Job 表示只能接收。這是 API 層的保護:producer 不能讀取 channel,consumer 不能寫入 channel。

【執行】non-blocking send 的取捨

non-blocking send 的核心規則是:送不出去時立即走 fallback,不等待接收端。它適合「寧可丟棄或記錄,也不要卡住呼叫端」的情境。

1select {
2case jobs <- job:
3    logger.Info("job queued", "id", job.ID)
4default:
5    logger.Warn("job queue full", "id", job.ID)
6}

這個策略的代價是資料可能被丟棄,所以必須記錄 log 或回傳明確錯誤。若資料不能丟,就不要用 default;讓送出端阻塞或回傳「系統忙碌」會更誠實。

關閉 channel

關閉 channel 的核心規則是:由送出端關閉,表示不會再有新資料。接收端可以用 range 讀到 channel 關閉:

 1func producer(out chan<- int) {
 2    defer close(out)
 3    for i := 0; i < 3; i++ {
 4        out <- i
 5    }
 6}
 7
 8func consumer(in <-chan int) {
 9    for value := range in {
10        fmt.Println(value)
11    }
12}

接收端不應關閉自己沒有所有權的 channel;否則送出端可能在送資料時遇到 panic。