waitgroup.add 必须在 goroutine 启动前调用,否则可能因 wait 先完成减法而 add 滞后导致“negative waitgroup counter”panic;正确做法是在 go 关键字前且确保执行的确定位置调用 add。

WaitGroup.Add 必须在 goroutine 启动前调用
如果在 go 语句内部、或 goroutine 已开始执行后才调用 WaitGroup.Add(1),极大概率触发 panic:「panic: sync: negative WaitGroup counter」。这是因为 WaitGroup.Wait() 可能已抢先完成计数器减法,而 Add 还没来得及加回来。
正确做法是:在 go 关键字之前,且确保该 Add 调用一定被执行(不能被条件跳过)。
- ✅ 正确:
var wg sync.WaitGroup for i := 0; i
- ❌ 错误:
<pre class="brush:php;toolbar:false;">for i := 0; i
Add 的参数不能为负数,且不能在 Wait 之后调用
WaitGroup.Add() 接收一个 <code>int,但传入负值会直接 panic:「panic: sync: negative WaitGroup counter」。同时,一旦 WaitGroup.Wait() 返回(即计数器归零),再调用 Add() 就属于未定义行为——Go 1.22+ 明确 panic,旧版本也可能 crash 或静默失败。
- 避免用
wg.Add(-1)模拟“撤销”;Done 才是唯一安全的减法方式 - 不要在
defer wg.Wait()之后、或多个Wait()之间穿插Add(),除非你明确控制了所有 goroutine 生命周期 - 若需动态增减任务,应拆分为多个独立 WaitGroup,或改用
errgroup.Group
循环中 Add 被跳过会导致 Wait 永久阻塞
常见疏漏是把 Add() 放在 if 分支里,而某些迭代不满足条件,导致 Wait() 等待一个永远不会 Done() 的计数器。
例如以下代码会在 i == 1 时跳过 Add,但循环仍启动了 goroutine(且无 Done),最终 Wait() 卡住:
<pre class="brush:php;toolbar:false;">for i := 0; i
- 确保每个
Add(1) 都有且仅有一个对应 <code>Done() - 优先用
defer wg.Done(),而不是裸写wg.Done(),避免 panic 中遗漏 - 调试时可临时加
fmt.Printf("Add %d, now %d\n", n, wg)(需反射读取,不推荐生产用)
并发调用 Add 是安全的,但没必要额外加锁
sync.WaitGroup 的 Add() 方法本身是并发安全的,底层用原子操作更新计数器。所以多个 goroutine 同时调用 Add() 不会出错——但这种模式通常意味着设计混乱。
- 典型反模式:主 goroutine 和子 goroutine 竞争调用 Add,难以追踪总数
- 更清晰的做法是:由任务发起方(通常是 main 或父 goroutine)统一批量 Add,子 goroutine 只负责 Done
- 若真需动态注册(如 worker 池接收新任务),应使用 channel + 单独 goroutine 管理计数,而非依赖并发 Add











