WaitGroup 必须在 goroutine 启动前调用 Add,且 Add 与 Done 需严格配对;Add 不能在 goroutine 内无锁调用;Done 应 defer 调用以避免遗漏;WaitGroup 不可复制,须传指针;它仅用于等待完成,不提供结果传递、超时或取消能力。

WaitGroup 必须在 goroutine 启动前 Add
很多人一上来就 go func() { wg.Done() }() 然后 wg.Wait(),结果程序立刻退出——因为 wg.Add(1) 没调用,或者调得太晚。WaitGroup 不会自动感知 goroutine 数量,必须显式告诉它“我要等几个”。Add 调用必须发生在 go 语句之前,且不能在 goroutine 内部执行(除非你用闭包+锁保护,但没必要)。
常见错误现象:wg.Wait() 立即返回、goroutine 根本没执行完、panic: sync: negative WaitGroup counter(Done 多于 Add)。
-
wg.Add(1)必须在go前,哪怕只启一个 goroutine - 如果启 N 个,
wg.Add(N)或循环 N 次wg.Add(1),别漏数 - 不要在 goroutine 里调
wg.Add(),除非你额外加锁,否则竞态风险高
Done 必须在 goroutine 退出前调用,且只能调一次
Done() 是 Add(-1) 的快捷写法,它不关心当前 goroutine 是否真干完了,只管减计数。如果函数提前 return 却忘了 wg.Done(),Wait() 就永远卡住;如果误调两次,直接 panic。
使用场景:带 error 处理、defer 清理、多出口函数。
- 最稳妥写法是开头就
defer wg.Done(),确保任何路径都执行 - 不要在
if err != nil { return }后面才写wg.Done(),容易漏 - 避免在循环体里反复
wg.Done(),除非你明确知道循环次数和 goroutine 对应关系
WaitGroup 不能复制,必须传指针
WaitGroup 是含 mutex 和原子计数器的结构体,值拷贝会导致内部状态分裂,引发 panic 或死锁。Go vet 会报 copying lock value of sync.WaitGroup,但运行时未必立刻暴露。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
典型错误:把 wg 当参数传进函数却用值传递、在 struct 字段里存值类型 WaitGroup、用 := 二次赋值。
- 函数参数必须是
*sync.WaitGroup,调用时传&wg - struct 中字段声明为
wg *sync.WaitGroup,初始化用&sync.WaitGroup{} - 切忌
newWg := wg这类赋值,这是复制,不是引用
WaitGroup 只适合“等待完成”,不适合传值或控制流程
WaitGroup 解决的是“我启动了一堆任务,等它们全结束再继续”,它不提供结果收集、错误聚合、超时控制或取消能力。想等结果?得配 channel;要超时?得用 context.WithTimeout;要等任意一个完成?那是 select + channel。
性能影响:WaitGroup 本身开销极小,但若滥用(比如每毫秒启百个 goroutine 并 Wait),瓶颈其实在调度和内存分配,而非 WaitGroup。
- 别用
wg.Wait()替代 channel 来取返回值,goroutine 间传数据走 channel 更安全自然 - 需要超时就别硬等,
select { case - WaitGroup 零值可用,不需要显式
new或&sync.WaitGroup{},直接声明var wg sync.WaitGroup即可
WaitGroup 看似简单,但 Add/Done 的时机和传参方式稍有偏差,就会导致阻塞、panic 或静默失败——这些都不是编译错误,而是运行时幽灵问题。真正难的不是写对一次,而是在嵌套调用、错误分支、重构迁移中始终守住那两条线:Add 在 go 前,Done 用 defer。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










