
sync.waitgroup 是 go 中轻量、高效等待多个 goroutine 完成的标准方式,适用于“主协程等待子协程全部结束”的场景;它不替代通道通信,而是与通道协同——前者解决同步计数,后者解决数据传递与控制流。
sync.waitgroup 是 go 中轻量、高效等待多个 goroutine 完成的标准方式,适用于“主协程等待子协程全部结束”的场景;它不替代通道通信,而是与通道协同——前者解决同步计数,后者解决数据传递与控制流。
在 Go 并发编程中,sync.WaitGroup 的核心职责是协作式等待(cooperative waiting):通过 Add()、Done() 和 Wait() 三步完成对 goroutine 生命周期的计数协调。它不是通用同步原语(如互斥锁),也不用于传递数据或建模业务逻辑流——这正是官方文档强调“Higher-level synchronization is better done via channels”的原因。
你的原始用法基本正确,但存在两个关键隐患,需立即修正:
✅ 正确写法要点:
-
wg.Add(1)必须在go启动前调用(避免竞态:若Done()先于Add()执行,会导致 panic); -
defer wg.Done()是良好实践,但需确保Done()在 goroutine 内部被调用(不能放在main中); -
禁止全局声明
wg:全局变量破坏封装性,易引发复用冲突和测试困难;应按需声明于作用域内。
? 修正后的标准示例:
func main() {
var wg sync.WaitGroup
for i := 0; i <p>⚠️ 注意事项:</p>
-
不要在循环中重复声明
wg(如每次迭代新建),否则Wait()将永远阻塞; -
闭包捕获循环变量需谨慎:原示例中
i未传入闭包,会导致所有 goroutine 使用相同i值(典型陷阱)。应显式传参(如上所示)或使用for _, v := range+v拷贝; -
超时控制需结合通道:
WaitGroup本身不支持超时。若需防止单点故障导致永久阻塞,应像答案中那样,用go wg.Wait(); select{}模式配合time.After实现带超时的等待。
? WaitGroup vs Channel 的本质区别:
| 维度 | sync.WaitGroup | Channel |
|--------------|-------------------------------------|--------------------------------------|
| 用途 | 协程完成信号计数(“是否做完?”) | 数据/事件传递、控制流编排(“做什么?何时做?”) |
| 所有权 | 无数据所有权,纯同步计数器 | 有明确发送/接收方,支持缓冲与关闭 |
| 组合能力 | 弱(仅 Wait/Add/Done) | 强(select、range、close、超时等) |
| 适用层级 | 底层基础设施(如启动/清理阶段同步) | 高层业务逻辑(如任务分发、结果聚合) |
? 总结:你的使用意图(类似 C++ join())完全契合 WaitGroup 的设计目标,这是完全合理且推荐的做法。不必强行替换为通道——除非你需要返回结果、响应取消、或构建更复杂的流水线模型。最佳实践是:用 WaitGroup 解决“等待完成”,用 channel 解决“传递结果”或“协调行为”,二者常配合使用,而非互斥替代。










