sync.WaitGroup 应声明为值类型(如 var wg sync.WaitGroup),禁用 nil 指针;Add() 必须在 goroutine 启动前调用,且不支持安全复用,推荐每次等待新建实例。

sync.WaitGroup 变量声明的三种常见写法
直接声明 sync.WaitGroup 类型变量即可,不需要调用构造函数或初始化方法——它内部是零值安全的结构体。只要没调用 Add() 就直接 Wait(),也不会 panic(会立即返回)。
推荐以下任一方式,语义清晰且无副作用:
-
var wg sync.WaitGroup—— 最常用,显式、易读,适合包级或函数内声明 -
wg := new(sync.WaitGroup)—— 返回指针,等价于&sync.WaitGroup{},但没必要,WaitGroup方法集定义在值类型上,指针和值都能调用 -
wg := sync.WaitGroup{}—— 字面量初始化,和var wg sync.WaitGroup行为完全一致
为什么不能用 var wg *sync.WaitGroup?
这是新手容易踩的坑:var wg *sync.WaitGroup 声明的是 nil 指针,后续调用 wg.Add(1) 或 wg.Wait() 会直接 panic:panic: runtime error: invalid memory address or nil pointer dereference。
因为 sync.WaitGroup 的所有方法(Add、Done、Wait)都要求接收者非 nil。它不是靠指针实现共享状态,而是靠内部字段(如 noCopy、state1)的原子操作,所以必须确保底层结构体已分配内存。
- 错:
var wg *sync.WaitGroup; wg.Add(1)→ panic - 对:
var wg sync.WaitGroup; wg.Add(1)→ 正常 - 对:
wg := &sync.WaitGroup{}; wg.Add(1)→ 也正常,但多此一举
WaitGroup 必须在 goroutine 启动前调用 Add
Add() 的调用时机非常关键:必须在启动 goroutine 之前完成,否则存在竞态风险。Go 官方文档明确警告——Add() 和 Go 的调用顺序不能颠倒。
典型错误写法:
go func() {
defer wg.Done()
// ... work
}()
wg.Add(1) // ❌ 错!Add 在 goroutine 启动后才调,wg.Wait() 可能提前返回
正确写法(Add 在 go 前):
wg.Add(1)
go func() {
defer wg.Done()
// ... work
}()
- 如果启动 N 个 goroutine,就调
wg.Add(N),不要在循环里每个 goroutine 都Add(1)—— 除非你明确要动态增减 -
Done()是Add(-1)的快捷方式,二者等价;但Done()更语义化,推荐用于“完成”场景
WaitGroup 的复用限制与生命周期注意点
sync.WaitGroup 不支持复用:一旦 Wait() 返回,内部计数归零,此时再调 Add(1) 是允许的,但必须确保没有其他 goroutine 正在执行 Wait() 或刚结束——否则行为未定义。
实践中更安全的做法是「一次 WaitGroup,一次用途」:
- 避免跨函数传递并重复
Add/Wait,尤其在不确定调用上下文时 - 不要在
Wait()返回后继续往里面塞新任务,除非你严格控制并发模型(比如自己加锁保护WaitGroup实例) - 如果需要多次等待不同批次任务,直接声明新的
var wg sync.WaitGroup,开销极小
真正容易被忽略的是:WaitGroup 的零值虽然可用,但它不是线程安全的「初始化」——你不能靠首次调用 Add 来触发隐式初始化,它的内存布局从声明那一刻起就已就绪。所以别想绕过声明,直接用未声明的变量名去调 xxx.Add(),编译都过不了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











