WaitGroup 必须先 wg.Add(n) 再启动 goroutine,且 Done() 须在 goroutine 内且仅调用一次;必须传指针避免复制;常与 channel 配合实现结果收集;复杂场景推荐 errgroup.Group。

WaitGroup 必须在启动 goroutine 前完成 Add
很多人写成先 go func() { ... wg.Done() }() 再 wg.Add(1),这会导致 Wait() 永远不返回,甚至 panic:「panic: sync: negative WaitGroup counter」。因为 Add 和 Done 不是原子配对操作,Done 可能早于 Add 执行。
正确顺序是:先调用 wg.Add(n),再启动 goroutine;每个 goroutine 结束前必须且只能调用一次 wg.Done()。
-
wg.Add(1)必须在go语句之前,或至少确保在任何Done()调用前完成 - 若启动多个协程,
wg.Add(n)可一次性加总数,不必循环里每次加 1(但加总值必须准确) - 不要在 goroutine 外部调用
Done(),也不要在同一个 goroutine 里调用多次
WaitGroup 不能复制,要传指针
Go 中 sync.WaitGroup 是含 mutex 字段的结构体,直接值传递会复制锁状态,导致未定义行为——常见现象是 Wait() 卡死、或 panic:「sync: WaitGroup is reused before previous Wait has returned」。
所有跨 goroutine 共享的 WaitGroup 实例,必须以 *sync.WaitGroup 形式传递。
- 函数参数声明为
wg *sync.WaitGroup,调用时传&wg - 闭包中引用外部
wg时,如果该闭包会被 go 启动,也要确保是引用原变量(即变量本身是地址可取的,比如局部变量或字段) - 切忌写
go work(wg)(wg是值),而应写go work(&wg)
WaitGroup + channel 组合比单独 Wait 更灵活
仅靠 wg.Wait() 只能等全部完成,无法获知中间结果、错误或进度。实际开发中常配合 channel 使用:
var wg sync.WaitGroup results := make(chan string, 10) errors := make(chan error, 5)for _, url := range urls { wg.Add(1) go func(u string) { defer wg.Done() resp, err := http.Get(u) if err != nil { errors <- err return } results <- resp.Status }(url) }
go func() { wg.Wait() close(results) close(errors) }()
// 非阻塞收结果 for r := range results { fmt.Println("OK:", r) }
-
WaitGroup管生命周期,channel管数据流,职责分离 - 务必在
wg.Wait()后关闭 channel,否则 range 会永远阻塞 - 若需等待并收集所有错误,可用带缓冲的
errorschannel + 单独 goroutine 关闭
替代方案:errgroup.Group 更适合带错误传播的并发
当任务可能失败且需要「任一错误即终止」或「收集全部错误」时,sync.WaitGroup 就显得笨重。golang.org/x/sync/errgroup 是更现代的选择:
g, _ := errgroup.WithContext(ctx)
for _, task := range tasks {
task := task // 避免循环变量复用
g.Go(func() error {
return doWork(task)
})
}
if err := g.Wait(); err != nil {
log.Fatal(err) // 任意一个 goroutine 返回非 nil error,Wait 就返回它
}
-
errgroup.Group自动处理 goroutine 启动和等待,无需手动Add/Done - 支持 context 取消,一个 goroutine 报错或 ctx Done,其余自动取消
- 默认只返回第一个 error;如需收集全部,得自己用 channel + WaitGroup 配合
WaitGroup 本身很简单,但它的使用边界非常明确:只负责“计数等待”,不处理错误、不传递数据、不响应取消。一旦需求超出这个范围,硬套 WaitGroup 容易写出难调试的竞态或死锁。











