WaitGroup 是原子计数器,只等待任务数归零而非 goroutine 结束;Add 必须在 Wait 前完成且不可动态追加,Done 应 defer 调用以确保执行,复制或负值操作会导致 panic 或死锁。

WaitGroup 不是“等 goroutine 结束”,而是等任务数归零
很多人一上来就写 go func() { wg.Done() }(),然后卡死在 wg.Wait() —— 因为 wg.Add(1) 没调用,或者调晚了。WaitGroup 本质是个原子计数器,它不感知 goroutine 生命周期,只认“Add 和 Done 是否配对、是否在 Wait 前完成”。
-
Add(n)必须在任何Wait()调用之前执行,且通常应在启动 goroutine 之前一次性加完(比如wg.Add(len(tasks))) - 不能在 goroutine 内部调
Add(1)来“动态追加任务”,除非你额外加锁——但这已违背 WaitGroup 的轻量设计初衷 - zero value(
var wg sync.WaitGroup)本身可用,但若误复制(如w2 := wg),会触发隐式拷贝,导致计数器失效且无报错
Done() 必须在 goroutine 内部调用,且推荐 defer
写成 defer wg.Done() 是最安全的惯用法;如果手动调用,必须确保每条执行路径(包括 panic、return、error early exit)都走到 Done(),否则 Wait() 永远阻塞——没有超时、不占 CPU、调试器里也看不出哪条路漏了。
- 别写
defer wg.Add(-1):语义错误(Add不是线程安全的减法),且违反 WaitGroup 使用契约 - 不要在主 goroutine 里调
Done(),它只该由对应子任务自己声明“我完成了” - 如果任务可能 panic,
defer wg.Done()仍有效(Go 的 defer 在 panic 时也会执行)
常见 panic 场景:计数器变负
panic: sync: negative WaitGroup counter 是最典型的运行时报错,说明 Done() 被多调了,或 Add() 漏了、传了负值。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 检查是否重复调用了
wg.Done()(比如 defer + 手动调用) - 确认没在循环外提前调
wg.Add(),又在循环内重复调(例如 for 循环里写了wg.Add(1),但外层又加了一次) - 禁止直接调
wg.Add(-1)或其他负 delta——Done()就是为此存在
WaitGroup 不保证执行顺序,也不处理错误传播
它只做一件事:等数字归零。哪怕某个 goroutine panic 了、某个任务失败了、某几个早返回了,只要 Done() 被调了,WaitGroup 就认为“完成”。后续逻辑需自行判断结果是否有效。
立即学习“go语言免费学习笔记(深入)”;
- 需要按序执行?用
channel或sync.Once+ 全局状态,别指望 WaitGroup - 需要收集错误?每个 goroutine 应把 error 写入共享的
chan error或切片(注意并发写保护) - 需要超时控制?
wg.Wait()本身不支持,得套一层select+time.After

















