Golang 並發模式。在撰寫或審查涉及 goroutine、channel、select、鎖、sync 原語、errgroup、singleflight、worker pool 或 fan-out/fan-in pipeline 的並發 Go 程式碼時使用。當你偵測到 goroutine 洩漏、競爭條件、channel 所有權問題,或需要在 channel 與 mutex 之間做選擇時也會觸發。
角色設定: 你是一位 Go 並發工程師。你假設每個 goroutine 都是一項負債,直到證明其必要性為止——正確性與無洩漏優先於效能。
模式:
- 撰寫模式 — 實作並發程式碼(goroutine、channel、sync 原語、worker pool、pipeline)。請依序遵循以下指示。
- 審查模式 — 審查 PR 中的並發程式碼變更。專注於 diff:檢查 goroutine 洩漏、缺少 context 傳遞、所有權違規以及未保護的共享狀態。依序進行。
- 稽核模式 — 稽核整個程式碼庫中現有的並發程式碼。如「並行化並發稽核」一節所述,最多使用 5 個平行子代理。
社群預設。 明確取代
samber/cc-skills-golang@golang-concurrency技能的團隊技能具有優先權。
Go 並發最佳實務
Go 的並發模型建立在 goroutine 和 channel 之上。Goroutine 雖然輕量,但並非免費——你產生的每個 goroutine 都是必須管理的資源。目標是結構化並發:每個 goroutine 都有明確的擁有者、可預測的結束方式以及正確的錯誤傳遞。
核心原則
- 每個 goroutine 都必須有明確的結束方式 — 沒有關閉機制(context、done channel、WaitGroup)會導致洩漏並累積,直到程序崩潰
- 透過通訊共享記憶體 — channel 明確傳遞所有權;mutex 保護共享狀態但使所有權隱含
- 在 channel 上傳送副本而非指標 — 傳送指標會產生隱藏的共享記憶體,違背 channel 的目的
- 只有傳送者能關閉 channel — 從接收端關閉會導致 panic(若傳送者在關閉後寫入)
- 指定 channel 方向(
chan<-、<-chan)— 編譯器在編譯時防止誤用 - 預設使用無緩衝 channel — 較大的緩衝區會掩蓋背壓;僅在有測量依據時使用
- 在 select 中永遠包含
ctx.Done()— 否則 goroutine 會在呼叫者取消後洩漏 - 避免在熱迴圈中重複使用
time.After— 每次呼叫都會分配計時器並造成不必要的開銷;對於長時間運行的迴圈,請使用time.NewTimer+Reset - 在測試中使用
go.uber.org/goleak追蹤 goroutine 洩漏
如需詳細的 channel/select 程式碼範例,請參閱 Channels and Select Patterns。
Channel vs Mutex vs Atomic
| 情境 | 使用 | 原因 |
|---|---|---|
| 在 goroutine 之間傳遞資料 | Channel | 傳達所有權轉移 |
| 協調 goroutine 生命週期 | Channel + context | 透過 select 進行乾淨關閉 |
| 保護共享結構體欄位 | sync.Mutex / sync.RWMutex |
簡單的臨界區段 |
| 簡單計數器、旗標 | sync/atomic |
無鎖,較低開銷 |
| 多讀取者、少寫入者的 map | sync.Map |
針對讀取密集型工作負載最佳化。並發 map 讀寫會導致程式直接崩潰 |
| 快取昂貴的計算結果 | sync.Once / singleflight |
執行一次或去重 |
WaitGroup vs errgroup
| 需求 | 使用 | 原因 |
|---|---|---|
| 等待 goroutine,不需要錯誤 | sync.WaitGroup |
發射後忘記 |
| 等待 + 收集第一個錯誤 | errgroup.Group |
錯誤傳遞 |
| 等待 + 在第一個錯誤時取消同儕 | errgroup.WithContext |
錯誤時取消 context |
| 等待 + 限制並發數 | errgroup.SetLimit(n) |
內建 worker pool |
Sync 原語快速參考
| 原語 | 使用案例 | 關鍵備註 |
|---|---|---|
sync.Mutex |
保護共享狀態 | 保持臨界區段簡短;絕不在 I/O 期間持有鎖 |
sync.RWMutex |
多讀取者、少寫入者 | 絕不從 RLock 升級到 Lock(會死結) |
sync/atomic |
簡單計數器、旗標 | 偏好型別化原子操作(Go 1.19+):atomic.Int64、atomic.Bool |
sync.Map |
並發 map,讀取密集型 | 無需明確鎖定;當寫入為主時使用 RWMutex+map |
sync.Pool |
重複使用暫用物件 | 在 Put() 前務必 Reset();減少 GC 壓力 |
sync.Once |
一次性初始化 | Go 1.21+:OnceFunc、OnceValue、OnceValues |
sync.WaitGroup |
等待簡單的 goroutine | Go 1.25+:對於不會 panic 且不需要錯誤傳遞的發射後等待任務,偏好使用 wg.Go(func(){ ... })。對於 Go <1.25 使用 Add/Done。對於錯誤/取消/限制,使用帶 context 的 errgroup。 |
x/sync/singleflight |
去重並發呼叫 | 防止快取雪崩 |
x/sync/errgroup |
Goroutine 群組 + 錯誤 | SetLimit(n) 取代手動實作的 worker pool |
如需詳細範例和反模式,請參閱 Sync Primitives Deep Dive。
並發檢查清單
在產生 goroutine 之前,請回答:
- [ ] 它如何結束? — context 取消、channel 關閉或明確訊號
- [ ] 我能通知它停止嗎? — 傳遞
context.Context或 done channel - [ ] 我能等待它嗎? —
sync.WaitGroup或errgroup - [ ] 誰擁有 channel? — 建立者/傳送者擁有並關閉
- [ ] 這是否應該改為同步? — 不要未經測量就加入並發
Pipeline 與 Worker Pool
關於 pipeline 模式(fan-out/fan-in、有界 worker、生成器鏈、Go 1.23+ iterator、samber/ro),請參閱 Pipelines and Worker Pools。
並行化並發稽核
在稽核大型程式碼庫的並發問題時,最多使用 5 個平行子代理(Agent 工具):
- 找出所有 goroutine 產生點(
go func、go method)並驗證關閉機制 - 搜尋沒有同步機制的可變全域變數和共享狀態
- 稽核 channel 使用方式——所有權、方向、關閉、緩衝區大小
- 找出迴圈中的
time.After、select 中缺少的ctx.Done()、無限制的 goroutine 產生 - 檢查 mutex 使用、
sync.Map、原子操作以及執行緒安全文件
常見錯誤
| 錯誤 | 修正 |
|---|---|
| 發射後忘記的 goroutine | 提供停止機制(context、done channel) |
| 從接收端關閉 channel | 只有傳送者能關閉 |
熱迴圈中的 time.After |
重複使用 time.NewTimer + Reset |
select 中缺少 ctx.Done() |
永遠在 select 中包含 context 以允許取消 |
| 無限制的 goroutine 產生 | 使用 errgroup.SetLimit(n) 或 semaphore |
| 透過 channel 共享指標 | 傳送副本或不可變值 |
在 goroutine 內部呼叫 wg.Add |
在 go 之前呼叫 Add——否則 Wait 可能提前返回 |
在 CI 中忘記 -race |
永遠執行 go test -race ./... |
| 在 I/O 期間持有 mutex | 保持臨界區段簡短 |
交叉參考
- -> 參閱
samber/cc-skills-golang@golang-performance技能以了解 false sharing、cache-line padding、sync.Pool熱路徑模式 - -> 參閱
samber/cc-skills-golang@golang-context技能以了解取消傳遞和逾時模式 - -> 參閱
samber/cc-skills-golang@golang-safety技能以了解並發 map 存取和競爭條件預防 - -> 參閱
samber/cc-skills-golang@golang-troubleshooting技能以了解除錯 goroutine 洩漏和死結 - -> 參閱
samber/cc-skills-golang@golang-design-patterns技能以了解優雅關閉模式 - -> 參閱
samber/cc-skills-golang@golang-continuous-integration技能以了解在 CI 中使用這些指南進行自動化 AI 驅動的程式碼審查
Go 1.26 實驗性 goroutine 洩漏分析
對於 Go 1.26 診斷,有一個實驗性的 goroutine 洩漏分析功能。它對於生產環境的洩漏調查很有用,但需要透過 GOEXPERIMENT=goroutineleakprofile 啟用;不要將其視為預設的穩定行為。
啟用實驗功能時的典型用法:
curl http://localhost:6060/debug/pprof/goroutineleak?debug=2
go tool pprof http://localhost:6060/debug/pprof/goroutineleak
保留現有工具:
- 測試:
go.uber.org/goleak - 執行時期計數:
runtime.NumGoroutine() - 堆疊傾印:
/debug/pprof/goroutine?debug=2 - 競爭檢查:
go test -race ./...






