WaitGroup 和 Context 必须配合使用才能安全处理带超时、取消和错误传播的并发任务;单用任一者均无法满足需求。

WaitGroup 和 Context 必须配合使用,单用任一者都无法安全处理带超时、取消和错误传播的并发任务。
sync.WaitGroup.Add 必须在 goroutine 启动前调用
这是最常踩的坑:把 Add(1) 放在 go 语句之后,或塞进 goroutine 内部,会导致 Wait() 永远阻塞或 panic。
- 正确顺序:先
wg.Add(1),再go func() { ... }() - 如果用循环启动多个 goroutine,
Add必须在循环体内、go之前 —— 不是循环外一次性Add(n)(除非你能提前确定 n 且不变化) - 传递
*sync.WaitGroup指针,否则 goroutine 里调用Done()修改的是副本,主 goroutine 看不到计数变化
context.WithTimeout 后必须 defer cancel()
漏掉 defer cancel() 会导致 context 泄漏,尤其在高频调用场景下积累 goroutine 和 timer 资源。
-
cancel函数不是可选的“善后”,而是资源释放的关键一环 - 即使你只用
ctx.Done()监听取消信号,也得调cancel()—— 它会关闭ctx.Done()channel 并清理内部 timer - 不要把
cancel()放到 goroutine 里调用(除非你明确控制生命周期),它应在父作用域退出时执行
WaitGroup + Context + channel 协同时的 channel 关闭时机
直接在 wg.Wait() 后 close(channel) 是错的 —— 因为 goroutine 可能还在往 channel 发数据,导致 panic: send on closed channel。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法是另起一个 goroutine 调
wg.Wait(),然后 close(channel),例如:go func() { wg.Wait() close(resultChan) }() - 接收端要用
for range resultChan,而不是<-resultChan循环 —— 否则无法感知 channel 关闭 - 如果需要收集全部结果(包括部分失败),别在第一个 error 就 return;要等所有 goroutine 结束再汇总
errgroup.Group 是 WaitGroup + Context 的标准封装,但有隐含限制
很多人直接上 errgroup.Group,却忽略了它默认不支持超时控制,且 Go 方法内部已调用 wg.Add(1),不能再手动 Add。
-
errgroup.WithContext(ctx)返回的 group 会自动监听ctx.Done()并取消剩余任务 - 但它的
Wait()不返回超时错误 —— 如果 ctx 超时,Wait()返回的是第一个非 nil error,或ctx.Err()(如context.DeadlineExceeded) - 若需区分“业务错误”和“超时错误”,得显式检查
errors.Is(err, context.DeadlineExceeded) - 不推荐在同一个 errgroup 中混用同步和异步逻辑;它的设计目标是“全成功 or 任一失败即停”,不适合需要部分结果的场景
真正难的不是写对某一行代码,而是判断该用 WaitGroup 还是 Context 做主控 —— 前者管“是否做完”,后者管“还能不能做”。两者边界模糊时,往往说明任务拆分粒度或错误处理路径没想清楚。


















