WaitGroup 必须先 Add 后 Wait,Add 要在 goroutine 启动前调用且数量准确;Done 应 defer 调用以确保执行;wg 不可复用,每次等待需新建实例;建议结合 context.WithTimeout 防卡死。

WaitGroup 忘了 Add 就 panic
调用 Wait() 前必须先调用 Add(),否则会直接 panic:"sync: negative WaitGroup counter"。这不是运行时检查不严,而是设计上要求你显式声明“我要等几个 goroutine”。常见错误是把 Add(1) 放在 goroutine 内部——那根本没用,因为 Wait() 已经在主线程里等了,而 goroutine 还没来得及执行 Add。
- 正确做法:在启动 goroutine 之前(或至少在
Wait()之前)调用wg.Add(1) - 如果启动 N 个 goroutine,就调用
wg.Add(N),别依赖循环里多次调用(容易漏或重复) - 不要在 goroutine 里调用
Add()来“动态补数”,这会让等待逻辑不可预测
Done 和 defer wg.Done() 的坑
Done() 必须和 Add() 配对,且每个 goroutine 结束前都要调用一次。最稳妥的写法是 defer wg.Done(),但要注意 defer 的执行时机——它在函数 return 前、任何 return 语句之后执行。如果你在 goroutine 函数里有多个 return 路径(比如 error 分支),忘写 defer 或写错位置,就会导致 Wait() 永远卡住。
- 所有 goroutine 入口函数第一行建议写
defer wg.Done(),别犹豫 - 别在 goroutine 里用
go func() { wg.Done() }()—— 这样Done()可能比 goroutine 自身还晚执行,甚至触发 panic - 如果 goroutine 里有 recover 逻辑,
defer wg.Done()仍有效;但若 panic 未被 recover,程序已崩,Done()不会执行,所以别靠 panic 来跳过
WaitGroup 不能复用,也不能跨 Wait 调用
sync.WaitGroup 不是状态机,它内部计数器归零后就不能再 Add(),否则 panic "sync: WaitGroup is reused before previous Wait has returned"。很多人想“一个 wg 多次用”,结果在第二次 Wait() 前调 Add() 就挂了。
- 每次需要等待一组新 goroutine,就用新的
sync.WaitGroup{}实例 - 不要把 wg 当全局变量反复
Add/Wait,尤其在长生命周期对象(如 HTTP handler)里 - 如果真要复用逻辑,封装成函数,让 wg 在函数内声明并返回,而不是传入传出
WaitGroup 和 context.WithTimeout 组合使用更安全
Wait() 是阻塞等待,没有超时机制。一旦某个 goroutine 卡死、忘了调 Done(),整个主线程就卡死。线上服务绝不能接受这种风险。
立即学习“go语言免费学习笔记(深入)”;
- 用
context.WithTimeout包一层,select等待wg.Wait()或 context Done - 超时后即使 wg 没完成,也能继续走错误处理路径(比如记录日志、返回降级响应)
- 注意:超时不会自动终止 goroutine,只是让你“不再等”,goroutine 仍在后台跑,需自行加 cancel 控制


















