# goroutine 與 channel

> goroutine 便宜但會 leak，channel 自帶同步語意，讓一個 goroutine 獨佔狀態就不用鎖，但純計數與小塊狀態用 mutex 或 atomic 更簡單，別迷信 channel。

原文：https://sheng.page/posts/go-goroutines-and-channels/ · 發布：2025-07-27 · 作者：Sheng

Go 的並行不是把 thread 包一層糖。Effective Go 濃縮成一句口號：「Do not communicate by sharing memory; instead, share memory by communicating.」這篇講清楚這句話，以及它什麼時候不適用。

## goroutine 很便宜

goroutine 不是 OS thread，是 runtime 排程的使用者級協程。初始 stack 約 2KB，按需成長收縮，所以同時開幾十萬個是常態。`go f()` 就起一個：

```go
go handle(conn) // 一個連線一個 goroutine，不用自己管 thread pool
```

但便宜不等於免費。goroutine 漏掉（沒有結束條件）會一直佔記憶體，這是 Go 最常見的 leak。每個長命 goroutine 都該有出口：`context` 取消或 channel 關閉。

## channel 與 select

channel 是 goroutine 之間的型別化管道，自帶同步語意。unbuffered 的送與收必須會合；buffered 的滿了才阻塞：

```go
ch := make(chan int)     // unbuffered：送收必須會合
ch := make(chan int, 64) // buffered：滿 64 才擋
```

`select` 同時等多個 channel，哪個 ready 走哪個；配 `ctx.Done()` 做取消、配 `time.After` 做逾時：

```go
select {
case v := <-in:
    use(v)
case <-ctx.Done():
    return ctx.Err() // 取消就收手
}
```

## 用通訊取代共享：actor 心法

讓一個 goroutine 獨佔那塊狀態，其他人用 channel 送訊息進去，不需要多個 goroutine 搶同一把鎖。狀態只被一個 goroutine 摸，就不需要鎖。這就是 Go 版的 actor。

gortex 的 WebSocket Hub 正是這樣：一個 `Hub.Run()` goroutine 擁有整個 clients map，所有註冊、退出、廣播都經 channel 進到這個唯一的 goroutine（〈[WebSocket Hub：Go 版 Actor 模型](/posts/gortex-websocket-actor-hub/)〉細講）。

## 什麼時候別用 channel

channel 不是萬靈丹。Effective Go 自己就說：如果你要的只是「保護一個變數，讓它一次只有一個 goroutine 存取」，用 mutex 把那個計數器包起來，往往更簡單直接。

純計數、簡單旗標、保護一小塊狀態 → `sync.Mutex` 或 `atomic` 更快也更好懂。channel 真正適合的是「傳遞所有權、事件流、跨 goroutine 協調」，不是每個共享都要套。這條伏筆扣回框架篇：gortex 的 metrics 計數器用 `atomic` 加分片鎖（見〈[併發計數與韌性：sharded metrics 與 circuit breaker](/posts/gortex-metrics-and-circuit-breaker/)〉），WebSocket Hub 用 channel，同一個專案，兩種工具按場景挑。

## Effective Go 精選：Concurrency

Effective Go 的 [Concurrency](https://go.dev/doc/effective_go#concurrency) 開頭就是那句「用通訊來共享記憶體，而不是用共享記憶體來通訊」，但它同一段也補了平衡的話：channel 不是要取代 mutex，有時候用一把鎖保護小狀態反而更貼切。重點是 CSP 的精神：用通訊協調並行，而不是反射性地到處上鎖；至於計數這種小事，挑最簡單、最清楚的那個就好。


## 補充筆記

- goroutine 是便宜的使用者級協程（初始約 2KB），但會 leak，每個都要有結束條件。
- channel 帶同步語意；`select` 多路等待，配 `ctx.Done()` / `time.After` 收尾。
- actor 心法：讓一個 goroutine 獨佔狀態，別人用 channel 送訊息，免鎖。
- 別迷信 channel：純計數、保護小狀態用 mutex / atomic 更簡單（伏筆：sharded metrics 與 WebSocket Hub 那兩篇）。
- 口訣是「用通訊共享記憶體」，但該用鎖時就用鎖。

延伸：本系列接著用這些觀念拆 gortex 框架，原始碼見 [yshengliao/gortex](https://github.com/yshengliao/gortex)。

---

> 想法與技術判斷出自 Sheng，和 Claude 一起起草 · 環境 Go 1.24（當時最新 release）· 本系列為事後回填整理 · 列入 20260613 blog 翻新計劃，新漆未乾。