Go 入门(20):sync.WaitGroup——等待一组 goroutine 完成
更新时间:2026-09-01。本文是
languages/go/beginner/入门层第 20 篇,接 goroutine 基础。如果你开了多个 goroutine 做事情,需要等它们全部做完再继续,用sync.WaitGroup。
本文要回答的问题
- WaitGroup 三个方法 Add/Done/Wait 分别做什么?
- Add 放哪里?Done 放哪里?Wait 放哪里?
- 闭包传 WaitGroup 为什么要传指针?
- 常见坑是什么?怎么避免?
一、基本用法
三个方法:
Add(n):增加计数器,通常在启动 goroutine 前调用,加1Done():计数器减 1,通常在 goroutine 结束时 defer 调用Wait():阻塞等待,直到计数器变成0
示例:等待三个 goroutine 都打印完:
go
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1) // 启动前加 1
go func(n int) {
defer wg.Done() // goroutine 结束减 1
fmt.Println("goroutine", n)
}(i)
}
wg.Wait() // 等全部完成
fmt.Println("all done")
}二、使用模式:开 N 个 goroutine,等全部完成
标准流程:
go
var wg sync.WaitGroup
for i := range tasks {
wg.Add(1)
go func(task Task) {
defer wg.Done()
// 干活
}(task)
}
wg.Wait()
// 全部干完了三、为什么要传指针给闭包?
如果把 wg 以值方式传给 goroutine,会拷贝一份,拷贝的 Done 改的是拷贝的计数器,原始的没变:
go
// ❌ 错误
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(i int, wg sync.WaitGroup) { // wg 是拷贝的
defer wg.Done() // Done 改的是拷贝,原始不变
}(i, wg)
}
wg.Wait()
}
// ✅ 正确
go func(i int, wg *sync.WaitGroup) { // 传指针
defer wg.Done()
}(i, &wg)四、常见坑:Add 放在 goroutine 里
go
// ❌ 错误:Add 在 goroutine 里,可能 wg.Wait() 已经跑完了,Add 还没执行
for i := 0; i < 3; i++ {
go func() {
wg.Add(1) // Add 这里太晚了
defer wg.Done()
}()
}
wg.Wait()规则: wg.Add(1) 必须放在 go 语句前面,保证在 wg.Wait() 之前计数器已经增加了。
五、实际场景:并发请求多个接口
go
urls := []string{
"https://api.example.com/user/1",
"https://api.example.com/user/2",
"https://api.example.com/user/3",
}
var wg sync.WaitGroup
results := make(chan string, len(urls))
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
resp, _ := http.Get(url)
results <- fmt.Sprintf("%s: %d", url, resp.StatusCode)
}(url)
}
wg.Wait()
close(results)开三个 goroutine 并发请求,等全部拿到结果再继续。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| Add 在 go 里面 | WaitGroup 提前返回 | Add 放在 go 前面 |
| WaitGroup 值拷贝 | 计数器一直不降到 0,Wait 永远等 | 传指针 |
| Add 一次加多个 | Add(n) 可以,只要加对总数就行 | 启动前加总数也行 |
| Wait 没等就继续 | 主 goroutine 提前退出,其他 goroutine 被杀 | 确保 Add 都做完再 Wait |
相关与延伸
下一篇:channel 基础——无缓冲 vs 有缓冲,发送接收 close;WaitGroup 底层基于信号量,进阶层讲;Go 并发原语都在
sync包。
一句话总结
sync.WaitGroup:等待一组 goroutine 全部完成;wg.Add(1) 启动前加,defer wg.Done() goroutine 结束减,wg.Wait() 阻塞等计数器到零;必须传指针不能传值,Add 必须在 go 之前调用;这是并发编程最基础的同步原语。