waitgroup 必须手动调用 add 和 done,add 需在 goroutine 启动前执行,defer wg.done() 应置于 goroutine 函数首行,禁止复制或重复使用未重置实例,它仅解决等待完成,不处理错误、超时或限流。

WaitGroup 不是“自动感知 goroutine 结束”的工具,必须手动调用 Add 和 Done,漏掉任意一个都会导致死锁或 panic。
为什么 WaitGroup.Add() 必须在启动 goroutine 之前调用
因为 Add 修改的是内部计数器,而 Wait 会阻塞直到计数器归零;如果在 goroutine 内部才调用 Add,Wait 可能早已返回,或因竞态永远等不到递减。
- 正确做法:在
go语句前调用wg.Add(1) - 错误写法:
go func() { wg.Add(1); defer wg.Done(); ... }()—— 此时Add和Done都在子 goroutine 中,Wait根本不知道要等谁 - 更隐蔽的错法:循环中漏了
Add,比如只在 if 分支里加,但部分迭代没走进去
defer wg.Done() 在 goroutine 中的位置很关键
defer 确保函数退出时执行 Done,但前提是 defer 所在的函数确实会退出。如果 goroutine 因 panic、无限循环或提前 return 没走到 defer 行,计数器就永远不会减,Wait 就卡住。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 推荐写法:把任务逻辑包进匿名函数,并确保
defer wg.Done()是第一行(或紧随参数捕获之后) - 示例:
wg.Add(1) go func() { defer wg.Done() // 放最前,哪怕后续 panic 也能触发 // ... 实际工作 }() - 避免在 goroutine 中用
os.Exit或直接调用runtime.Goexit(),它们会绕过 defer
WaitGroup 不能被复制,也不能重复使用未重置的实例
sync.WaitGroup 是含 mutex 和原子计数器的结构体,复制后两个实例互不影响,原 wg 的 Wait 不会感知副本的 Done;而一旦 Wait 返回,内部状态未清零,再次 Add 会导致 panic(Go 1.20+ 默认启用 race 检测时尤其明显)。
- 禁止:
wg2 := wg或作为函数参数值传递 - 正确传参方式:传指针
*sync.WaitGroup - 重复使用需重置:调用
wg = sync.WaitGroup{}(新建)或reflect.ValueOf(&wg).Elem().UnsafeAddr()非常规手段不推荐;更稳妥的是每次用新变量 - 常见误用场景:在 for 循环外声明 wg,循环内反复 Add/Wait,第二次 Wait 前没重置 → panic: sync: WaitGroup is reused before previous Wait has returned
最常被忽略的点是:WaitGroup 本身不处理 goroutine 内部错误,也不限制并发数;它只解决“等待完成”这一个维度。如果你需要带超时、错误聚合或限流,得组合 context.Context、errgroup.Group 或带缓冲 channel。










