waitgroup 必须先 add 再启动 goroutine,否则因计数器负值 panic;必须传指针而非值;常与 channel 配合使用收结果和错误;复杂场景应改用 errgroup.group。

WaitGroup 必须先 Add 再启动 goroutine,否则会 panic
很多人写成 go func() { wg.Done() }() 之后才调用 wg.Add(1),结果程序直接崩溃,报错:panic: sync: negative WaitGroup counter。这是因为 Add 和 Done 不是原子配对操作,goroutine 可能抢在 Add 执行前就跑完并调了 Done,导致计数器变成负数。
-
wg.Add(n)一定要在go语句之前,或至少确保在任何Done()调用前完成 - 如果启动多个协程,可以一次性
wg.Add(len(tasks)),不必在循环里反复Add(1)(但总数必须准确) - 切忌把
wg.Add(1)放进 goroutine 里——它不属于“任务逻辑”,而是“调度声明”
WaitGroup 不能传值,必须传指针
直接传 wg(值类型)会导致 wg.Wait() 卡死,或者报错:sync: WaitGroup is reused before previous Wait has returned。因为 sync.WaitGroup 内部含 mutex 字段,值传递会复制锁状态,破坏同步语义。
- 函数参数必须声明为
wg *sync.WaitGroup,调用时传&wg - 闭包中引用外部
wg时,要确保该变量可取地址(比如是局部变量、结构体字段),不能是临时计算值 - 常见错误写法:
go work(wg)→ 正确写法:go work(&wg)
WaitGroup + channel 是标准搭配,单用 Wait() 很难收结果
wg.Wait() 只管“是否全干完”,不管“干得怎么样”——没返回值、不传错误、不反馈进度。真实场景几乎都要配合 channel 做数据流转。
- 用带缓冲的
results := make(chan string, N)收结果,避免阻塞 - 用另一个
errors := make(chan error, M)收错误,记得单独起 goroutine 在wg.Wait()后close() - 务必等
wg.Wait()完成后再close(results)和close(errors),否则for range会永远卡住
复杂场景别硬刚 WaitGroup,改用 errgroup.Group
当你要并发执行一堆可能出错的操作,并且希望任意一个失败就快速退出(cancel)、同时收集所有错误时,sync.WaitGroup 就力不从心了。它不支持取消、不聚合错误、也不管理上下文。
-
errgroup.Group是官方推荐替代方案,自动绑定context.Context - 它底层仍用
WaitGroup,但封装了错误传播、提前终止、结果合并等逻辑 - 如果你发现要自己手写
done通道 +select+context.WithCancel,那基本就是该换errgroup的信号
最常被忽略的一点:WaitGroup 的生命周期和 channel 的关闭时机必须严格对齐。不是“谁先写完谁关 channel”,而是“等 wg.Wait() 返回后,再 close 所有相关 channel”。这点一旦错,整个流程就静默卡死,debug 花的时间远超写代码本身。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











