waitgroup仅用于计数归零,add必须在go前调用,done推荐defer避免漏调,不可重用,禁止传递指针给已启动goroutine,循环中需传参避免共享同一wg实例。

WaitGroup 不是万能的等待工具,它只解决“计数归零”这一件事;用错场景、调用顺序错乱、重复使用,都会让程序卡死或 panic。
WaitGroup 的 Add 和 Done 必须成对且顺序正确
最常见错误是 Add 放在 go 语句之后,导致协程启动时计数器还没加,Wait 可能提前返回;或者 Done 被漏掉、被多次调用,造成计数器永远不归零或负值 panic。
-
Add必须在go之前调用,确保计数器先到位 -
Done推荐用defer wg.Done(),避免因 return、panic 漏调 - 不能在
Wait返回后继续调用Add—— WaitGroup 不可重用 - 如果协程可能 panic,
defer wg.Done()仍会执行(Go 运行时保证 defer 在 panic 时也运行)
WaitGroup 不能传递指针给已启动的 goroutine
传参时若把 *sync.WaitGroup 直接作为闭包变量捕获,而该变量在循环中复用(比如 for 循环里没传参),会导致所有 goroutine 共享同一个地址,但实际操作的是不同生命周期的 wg 实例 —— 表现为部分 Done 无效、Wait 卡住。
- 错误写法:
for i := 0; i —— <code>i和wg都被闭包共享,最后一次迭代覆盖全部 - 正确写法:显式传参,
go func(id int, w *sync.WaitGroup) { defer w.Done() }(i, &wg) - 更安全做法:把
wg作为函数参数传入 worker 函数,而非依赖闭包捕获
WaitGroup 与超时控制必须配合 channel 才能生效
WaitGroup 本身没有超时机制。Wait 会一直阻塞,直到计数器归零。一旦某个 goroutine 挂起、死锁或网络卡住,主流程就彻底卡死。
- 必须结合
select+time.After或context.WithTimeout做兜底 - 示例:
select { case - 注意:不能在
Wait调用外直接判断超时,因为Wait是阻塞的;需另起 goroutine 调用Wait并发通知
WaitGroup 计数器归零后状态不可恢复
很多人误以为 WaitGroup 可以像信号量一样重置复用。实际上,官方文档明确标注:A WaitGroup must not be copied after first use,且归零后再次 Add 属于未定义行为(Go 1.22+ 会 panic)。
- 不要试图通过
reflect或 unsafe 修改内部字段来“重置” - 需要多次等待?每次新建一个
var wg sync.WaitGroup - 高频复用场景(如 HTTP handler 中)建议局部声明,避免跨请求复用
- 结构体嵌入
sync.WaitGroup时尤其危险——容易意外复制或导出
真正难的不是调用三个方法,而是理解 WaitGroup 只是一个带原子计数的“门禁”,它不感知任务内容、不处理错误、不提供上下文取消能力。这些都得靠你手动补全。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











