Wait 通过原子读取 state1 高32位判断任务计数是否为0,为0则立即返回,否则增等待者计数并阻塞于信号量;Done()归零时唤醒所有等待者。
Wait 方法如何判断所有 goroutine 已完成
wait 不轮询、不睡眠、不 busy-wait,它靠的是原子状态 + 信号量唤醒。内部读取 state1 的高 32 位(任务计数),一旦为 0 就立即返回;否则把低 32 位(等待者计数)原子加 1,然后调用 runtime_semacquire(&wg.sema) 阻塞在底层信号量上。只有当某个 done() 执行后发现计数归零,才会顺带调用 runtime_semrelease(&wg.sema, uint32(waiters), false) 唤醒全部等待者。
为什么 Wait 不能在 Add 之前调用
因为 Wait 依赖高 32 位的初始值做判断。如果先 Wait() 再 Add(1),此时高 32 位是 0,Wait 立刻返回,根本不会阻塞——子 goroutine 还没启动,主协程已继续执行,造成逻辑错乱。常见错误现象:All workers done. 在任何 worker 打印前就输出。
- 必须保证
Add(n)在所有go启动前完成(哪怕只是go func() { ... }()的那一行之前) - 不能把
Add放进 goroutine 里,否则竞争条件无法避免 - 若需动态增减(如从 channel 拉任务),应提前预估上限或改用其他同步机制(如
errgroup.Group)
Wait 被唤醒后是否一定安全访问共享数据
不自动保证。Wait 返回只说明所有被 Add 过且对应 Done() 的 goroutine 已退出,但不承诺它们的内存写操作对主 goroutine 可见——除非你用了显式同步(如 mutex、channel send/recv 或 atomic 操作)。Go 内存模型中,Done() “synchronizes before” Wait() 的返回,但这仅针对 WaitGroup 自身状态,不延伸到你自己的变量。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 若子 goroutine 修改了某个
result切片,主 goroutine 在Wait()后直接读,结果未定义(可能看到旧值) - 安全做法:用
sync.Mutex保护该变量,或让子 goroutine 通过channel发送结果(channel send/recv 本身构成同步点) - 不要依赖
Wait的返回顺序推断数据就绪顺序
Wait 调用后能否再调用 Add
可以,但必须确保没有其他 goroutine 正在执行 Wait 或即将进入 Wait。因为 Add 修改的是同一个 state1,而 Wait 在检查计数时会读取该字段——若 Add 在 Wait 读取之后、阻塞之前发生,可能导致 Wait 错误地认为计数已为 0 并立即返回。
- 典型危险场景:两个 goroutine 并发调用
Wait和Add,无额外锁保护 - Go 官方文档明确警告:
A WaitGroup must not be copied after first use,也隐含了“状态变更需受控”的意思 - 实践中,
Add应集中出现在启动逻辑早期,Wait出现在收尾阶段,二者天然隔离
Wait() 返回 = 所有数据已就绪。

















