
WaitGroup.Add() 必须在 goroutine 启动前调用
很多人写 WaitGroup.Add(1) 放在 goroutine 里,结果 Wait() 立刻返回,或者 panic:`panic: sync: negative WaitGroup counter`。这是因为 Add() 和 Done() 不是线程安全的配对操作——Add() 必须在 go 语句之前执行,否则可能被还没启动的 goroutine 漏掉。
- ✅ 正确:先
wg.Add(1),再go func() { ... wg.Done() }() - ❌ 错误:在 goroutine 内部第一行写
wg.Add(1),此时Wait()可能已判定计数为 0 - ⚠️ 注意:
Add()传负数会直接 panic,别用wg.Add(-1)代替Done()
WaitGroup 不能复用(尤其不能在 Wait() 后继续 Add)
WaitGroup 不是循环用的工具,它设计为“一次初始化、一批 goroutine、一次等待”。调用 Wait() 返回后,内部计数器归零,但结构体状态未重置;如果接着 Add(),就会触发 `negative counter` panic。
- 常见错误场景:for 循环里反复
wg.Add(1)+go+wg.Wait(),第二轮必崩 - 正确做法:每次需要等待新一批任务,就声明新的
sync.WaitGroup变量 - 替代方案:用
sync.Pool缓存*sync.WaitGroup实例,但得确保无并发访问风险
WaitGroup.Done() 忘记调用或调用多次的后果
Done() 是 Add(-1) 的快捷方式,但它不校验当前计数是否 > 0。少调用 → Wait() 永远阻塞;多调用 → 计数变负 → panic。
- 典型漏调用:recover 捕获异常后忘了
wg.Done(),尤其在有 error return 的分支里 - 典型多调用:defer
wg.Done()+ 手动wg.Done(),或 defer 放错位置导致执行两次 - 建议:一律用
defer wg.Done(),且只放函数开头(不是 goroutine 内部嵌套函数)
WaitGroup 和 context.WithTimeout 组合时的超时处理
WaitGroup.Wait() 本身不支持超时,强行用 select + time.After 等待,容易忽略 goroutine 实际是否结束,造成资源泄漏或重复释放。
- 不要这样:
select { case —— <code>Wait()仍会阻塞后续逻辑 - 推荐组合:
context.WithTimeout控制整体生命周期,goroutine 内部监听ctx.Done()并主动退出,最后再wg.Wait() - 关键点:WaitGroup 只管“是否完成”,不负责“是否该停”;超时决策必须由业务逻辑承担,不能依赖
Wait()返回
WaitGroup 看似简单,真正难的是和错误路径、超时、复用、defer 顺序这些细节咬合——漏一个,程序就卡死或崩溃,而且问题往往不出现在测试环境。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











