其他
编写 Go 并发代码时:管理 goroutine 生命周期…
在 Go 中编写 goroutine、channel、mutex、atomic 或线程安全代码,并需要并行化、修复 data race、保护共享状态时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
编写 Go 并发代码时:管理 gorou通用
解决思路
- 按场景查阅参考文档:启动、停止或等待 goroutine 时读 references/GOROUTINE-PATTERNS.md。
- 按场景查阅参考文档:在 mutex、atomic、channel 和 once-like primitives 之间选择时读 references/SYNC-PRIMITIVES.md。
- 按场景查阅参考文档:考虑 channel-backed 或 sync.Pool-style 复用时读 references/BUFFER-POOLING.md。
- 按场景查阅参考文档:需要 worker pools、pipelines、errgroup 和大量取消模式时读 references/ADVANCED-PATTERNS.md。
- 生成 goroutine 时,明确它何时退出或是否会退出。
- 避免 goroutine 因阻塞在 channel 发送/接收而泄漏;注意 GC 不会终止阻塞的 goroutine,即使没有其他 goroutine 持有该 channel 的引用。
- 注意即使没有泄漏,在途 goroutine 也可能导致 panic(向已关闭 channel 发送)、data race、内存问题和资源泄漏。
- 为每个 goroutine 提供停止机制:可预测的结束时间、取消信号,或两者。
- 确保代码能等待 goroutine 结束。
- 不要在 init() 中启动 goroutine;改为暴露生命周期方法(Close、Stop、Shutdown)。
- 保持同步作用域受限:限制在函数作用域,并把逻辑拆成同步函数。
- 使用 sync.WaitGroup(Go 1.25+ 可用 wg.Go)等待 goroutine:var wg sync.WaitGroup; for item := range queue { item := item; wg.Go(func() { process(ctx, item) }); }; wg.Wait()。
- 避免这种无停止、无等待的 goroutine:go func() { for { flush(); time.Sleep(delay) } }()。
- 用 go.uber.org/goleak 测试 goroutine 泄漏。
- 遵循原则:绝不在不知道 goroutine 如何停止的情况下启动它。
- 不要通过共享内存来通信;要通过通信来共享内存。
- 用 channel 做所有权转移和编排:当一个 goroutine 生产值、另一个 goroutine 消费值时使用。
- 当多个 goroutine 访问共享状态且 channel 会增加不必要的复杂性时,使用 mutex。
- 默认使用 channel;当问题本质是保护共享数据结构(如 cache 或 counter)而非在 goroutine 间传递数据时,回退到 sync.Mutex / sync.RWMutex。
- 优先使用同步函数而不是异步函数。
- 同步函数的好处:goroutine 生命周期局部化、更易推理;避免泄漏和竞态;更易测试;调用方更灵活。
- 让调用方在需要时添加并发;不要在库/函数中预先引入难以移除的并发。
- 利用 sync.Mutex 和 sync.RWMutex 的零值有效性;几乎不需要 mutex 指针。
- 使用 var mu sync.Mutex,而不是 mu := new(sync.Mutex)。
- 不要嵌入 mutex;使用命名的 mu 字段,使 Lock/Unlock 保持为实现细节,而不是导出 API。
- 尽可能指定 channel 方向。
- 用方向防止错误(编译器能捕获关闭 receive-only channel)、传达所有权并自文档化。
- 按方向编写函数签名:func produce(out chan<- int); func consume(in <-chan int); func transform(in <-chan int, out chan<- int)。
- channel 大小应为 0(unbuffered)或 1。
- 如果 channel 大小不是 0 或 1,必须说明:如何确定该大小。
- 如果 channel 大小不是 0 或 1,必须说明:什么能防止 channel 在负载下被填满。
- 如果 channel 大小不是 0 或 1,必须说明:writers 阻塞时会发生什么。
- 使用 c := make(chan int)(unbuffered)或 c := make(chan int, 1)(size one);避免无理由的 c := make(chan int, 64)。
- 使用 atomic.Bool、atomic.Int64 等类型安全原子操作(stdlib sync/atomic since Go 1.19,或 go.uber.org/atomic)。
- 兼容性:原子操作示例在可用时使用标准库 typed atomics;若项目已依赖 go.uber.org/atomic,也可使用它。
- 不要用原始 int32/int64 字段配合 atomic.StoreInt32 等,因为容易在某些代码路径忘记原子访问。
- 使用 var running atomic.Bool; running.Store(true); running.Load(),而不是 var running int32 // atomic; atomic.StoreInt32(&running, 1); running == 1 // race!。
- 当线程安全从操作类型不明显时,记录并发行为。
- 记录并发时,遵循 Go 用户假设:只读操作并发安全,修改操作不安全。
- 在 Read vs mutating 不清楚时记录并发,例如 Lookup 会修改 LRU 状态。
- 在 API 提供同步时记录并发,例如 thread-safe clients。
- 在 interface 有并发要求时,在类型定义中记录并发。
- context.Context 指南(参数位置、结构体存储、自定义类型、派生模式)参见 go-context skill。
- 用带缓冲 channel 作为 free list 复用已分配 buffers。
- 使用 leaky buffer 模式:用 select 和 default 进行非阻塞操作。
- 相关技能路由:通过 goroutine 传递取消、deadlines 或 request-scoped values 时参见 go-context。
- 相关技能路由:从 goroutine 传播错误或使用 errgroup 时参见 go-error-handling。
- 相关技能路由:在 API 边界保护共享状态或使用 defer 清理时参见 go-defensive。
- 相关技能路由:为含 sync primitives 的类型选择 receiver types 时参见 go-interfaces。
适用边界 · 注意事项
- 不适用于 context.Context 模式(参数位置、结构体存储、自定义类型、派生模式);这些场景用 go-context。
- 不替代错误处理:从 goroutine 传播错误或使用 errgroup 时用 go-error-handling。
- 不替代 API 边界防御、defer 清理和含 sync primitives 类型的 receiver 设计;分别用 go-defensive、go-interfaces。