waitgroup 只等待计数器归零,add 必须在 goroutine 启动前调用,done 必须严格匹配 add 总和,不可复制或复用,否则引发 panic 或死锁。

WaitGroup 不是用来“等协程结束”的魔法开关,它只做一件事:等计数器归零。用错就 panic 或死锁,没有中间态。
sync.WaitGroup.Add 必须在 goroutine 启动前调用
这是最常触发 sync: WaitGroup misuse: Add called concurrently with Wait 的原因。现象是程序偶尔 panic,或 wg.Wait() 立即返回,后续 wg.Done() 导致负计数 panic。
-
wg.Add(1)和wg.Wait()是竞态敏感操作,必须由主协程串行控制 - 绝对不要写成:
go func() { wg.Add(1); defer wg.Done(); }() - 正确顺序只能是:
wg.Add(1); go func() { defer wg.Done(); }() - 循环启动多个协程时,
Add(1)必须在go语句之前,且一一对应 —— 少一次就卡住,多一次就 panic
Done 调用必须严格匹配 Add 总和
wg.Done() 等价于 wg.Add(-1),底层对计数器做原子减法。一旦变成负数,立刻 panic。
- 分支逻辑里 early return 没加
defer wg.Done()→ 计数卡住,wg.Wait()永久阻塞 - 重复调用
Done()(比如defer放错位置、或手动调 +defer双重调用)→ 计数变负 → panic - 闭包捕获循环变量时,多个 goroutine 可能共享同一变量,导致某个
Done()被跳过或误调 —— 应显式传参:go f(x),而不是go f()
WaitGroup 不可复制、不可重用
结构体字段赋值、函数参数传值、append 到切片、甚至作为 map value 存储,都会触发浅拷贝,导致内部 state1 字段分裂,计数器失效。
- 错误示例:
func handle(wg sync.WaitGroup)或subWg := wg - 正确做法:所有跨作用域使用都传
*sync.WaitGroup,比如doWork(id int, wg *sync.WaitGroup) - 官方文档明确:计数器归零后,
WaitGroup进入终态;再次调用Add()虽不 panic,但Wait()在非零计数下才安全;强行复用等于埋雷,尤其在 HTTP handler 这类长生命周期对象中 - 真要循环等待?每次都要新声明:
var wg sync.WaitGroup
真正难的不是记住三步,而是当代码嵌套了 channel、context、error 处理、超时控制之后,还能让 Add/Done 的配对关系一眼可验 —— 这时候别硬撑,拆函数、加注释、用静态检查工具,比靠脑子记靠谱。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











