golang-concurrency

golang-concurrency

热门

Golang 并发模式。在编写或审查涉及 goroutine、channel、select、锁、同步原语、errgroup、singleflight、工作池或扇出/扇入管道的并发 Go 代码时使用。当检测到 goroutine 泄漏、竞态条件、channel 所有权问题,或需要在 channel 和互斥锁之间做出选择时也会触发。

2261Star
150Fork
更新于 2026/6/6
SKILL.md
只读
名称
golang-concurrency
描述

Golang 并发模式。在编写或审查涉及 goroutine、channel、select、锁、同步原语、errgroup、singleflight、工作池或扇出/扇入管道的并发 Go 代码时使用。当检测到 goroutine 泄漏、竞态条件、channel 所有权问题,或需要在 channel 和互斥锁之间做出选择时也会触发。

角色: 你是一名 Go 并发工程师。你假设每个 goroutine 在未被证明必要之前都是一种负担——正确性和无泄漏优先于性能。

模式:

  • 编写模式 — 实现并发代码(goroutine、channel、同步原语、工作池、管道)。遵循下面的顺序指令。
  • 审查模式 — 审查 PR 中的并发代码变更。关注差异:检查 goroutine 泄漏、缺少上下文传播、所有权违规以及未保护的共享状态。顺序执行。
  • 审计模式 — 审计整个代码库中的现有并发代码。按照“并行化并发审计”部分所述,使用最多 5 个并行子代理。

社区默认。 明确覆盖 samber/cc-skills-golang@golang-concurrency 技能的公司技能优先。

Go 并发最佳实践

Go 的并发模型基于 goroutine 和 channel。Goroutine 很廉价但并非免费——你创建的每个 goroutine 都是必须管理的资源。目标是结构化并发:每个 goroutine 都有明确的所有者、可预测的退出和正确的错误传播。

核心原则

  1. 每个 goroutine 必须有明确的退出方式 — 没有关闭机制(context、done channel、WaitGroup),它们会泄漏并累积直到进程崩溃
  2. 通过通信共享内存 — channel 显式传递所有权;互斥锁保护共享状态但使所有权隐式
  3. 在 channel 上发送副本,而非指针 — 发送指针会创建不可见的共享内存,违背了 channel 的目的
  4. 只有发送方关闭 channel — 从接收方关闭会导致发送方在关闭后写入时 panic
  5. 指定 channel 方向chan<-<-chan)— 编译器在构建时防止误用
  6. 默认使用无缓冲 channel — 较大的缓冲区掩盖了背压;仅在经过测量的理由下使用
  7. 始终在 select 中包含 ctx.Done() — 没有它,goroutine 会在调用者取消后泄漏
  8. 避免在热循环中重复使用 time.After — 每次调用都会分配一个定时器并产生不必要的开销;对于长时间运行的循环,使用 time.NewTimer + Reset
  9. 在测试中使用 go.uber.org/goleak 跟踪 goroutine 泄漏

有关详细的 channel/select 代码示例,请参阅 Channels and Select Patterns

Channel 与互斥锁与原子操作

场景 使用 原因
在 goroutine 之间传递数据 Channel 传达所有权转移
协调 goroutine 生命周期 Channel + context 通过 select 实现干净关闭
保护共享结构体字段 sync.Mutex / sync.RWMutex 简单的临界区
简单计数器、标志 sync/atomic 无锁,开销更低
多读少写的 map sync.Map 针对读密集型工作负载优化。并发 map 读写会导致硬崩溃
缓存昂贵计算 sync.Once / singleflight 执行一次或去重

WaitGroup 与 errgroup

需求 使用 原因
等待 goroutine,不需要错误 sync.WaitGroup 即发即忘
等待 + 收集第一个错误 errgroup.Group 错误传播
等待 + 在第一个错误时取消兄弟任务 errgroup.WithContext 错误时取消上下文
等待 + 限制并发 errgroup.SetLimit(n) 内置工作池

同步原语快速参考

原语 用例 关键说明
sync.Mutex 保护共享状态 保持临界区简短;绝不在持有锁时进行 I/O
sync.RWMutex 多读少写 绝不要将 RLock 升级为 Lock(死锁)
sync/atomic 简单计数器、标志 优先使用类型化原子操作(Go 1.19+):atomic.Int64atomic.Bool
sync.Map 并发 map,读密集型 无显式锁定;当写操作占主导时使用 RWMutex+map
sync.Pool 重用临时对象 始终在 Put() 前调用 Reset();减少 GC 压力
sync.Once 一次性初始化 Go 1.21+:OnceFuncOnceValueOnceValues
sync.WaitGroup 等待简单 goroutine Go 1.25+:对于不会 panic 且不需要错误传播的即发即等任务,优先使用 wg.Go(func(){ ... })。对于 Go <1.25,使用 Add/Done。对于错误/取消/限制,使用带上下文的 errgroup
x/sync/singleflight 去重并发调用 缓存雪崩预防
x/sync/errgroup Goroutine 组 + 错误 SetLimit(n) 替代手写工作池

有关详细示例和反模式,请参阅 Sync Primitives Deep Dive

并发检查清单

在创建 goroutine 之前,回答以下问题:

  • [ ] 它如何退出? — 上下文取消、channel 关闭或显式信号
  • [ ] 我能通知它停止吗? — 传递 context.Context 或 done channel
  • [ ] 我能等待它吗?sync.WaitGrouperrgroup
  • [ ] 谁拥有 channel? — 创建者/发送者拥有并关闭
  • [ ] 这应该是同步的吗? — 不要在没有测量需求的情况下添加并发

管道和工作池

有关管道模式(扇出/扇入、有界工作器、生成器链、Go 1.23+ 迭代器、samber/ro),请参阅 Pipelines and Worker Pools

并行化并发审计

在审计大型代码库中的并发时,使用最多 5 个并行子代理(Agent 工具):

  1. 查找所有 goroutine 创建(go funcgo method)并验证关闭机制
  2. 搜索没有同步的可变全局变量和共享状态
  3. 审计 channel 使用——所有权、方向、关闭、缓冲区大小
  4. 查找循环中的 time.After、select 中缺少的 ctx.Done()、无限制的创建
  5. 检查互斥锁使用、sync.Map、原子操作和线程安全文档

常见错误

错误 修复
即发即忘的 goroutine 提供停止机制(context、done channel)
从接收方关闭 channel 只有发送方关闭
热循环中的 time.After 重用 time.NewTimer + Reset
select 中缺少 ctx.Done() 始终在 context 上 select 以允许取消
无限制的 goroutine 创建 使用 errgroup.SetLimit(n) 或信号量
通过 channel 共享指针 发送副本或不可变值
在 goroutine 内部调用 wg.Add go 之前调用 Add——否则 Wait 可能提前返回
在 CI 中忘记 -race 始终运行 go test -race ./...
持有互斥锁进行 I/O 保持临界区简短

交叉引用

  • -> 参见 samber/cc-skills-golang@golang-performance 技能,了解伪共享、缓存行填充、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 ./...

参考