WaitGroup.Add()必须在启动goroutine前调用,且参数>0;不可在goroutine中调用Add();defer wg.Done()不能漏括号;必须传指针避免复制;不解决数据竞争,共享变量需额外同步。
Add()
常见错误是先开 goroutine,再在 g

WaitGroup.Add(1) —— 这会导致 Wait() 可能永远阻塞,或 panic:「negative WaitGroup counter」。因为 Add() 和 Done() 不是原子配对操作,必须由主线程提前声明任务数。
Add() 必须在 go 语句之前调用,且参数 > 0sync.Map + 计数器Add(0) 合法但无意义;传负数直接 panicWaitGroup.Done() 而不加括号写成 defer wg.Done(漏掉括号)是静默错误:函数未执行,Done() 被当值传递,最终计数器不减,Wait() 永远不返回。Go 编译器不会报错,运行时行为完全异常。
go func() {
defer wg.Done() // ✅ 正确:带括号,实际调用
// ... work
}()
go func() {
defer wg.Done // ❌ 错误:只是取函数地址,不调用
// ... work
}()
sync.WaitGroup 包含 mutex 等非可复制字段,值拷贝会破坏内部状态。常见于:将 wg 作为参数传给函数却用了值传递、在循环中创建临时 wg 变量。
*sync.WaitGroup,不是 sync.WaitGroup
[]*sync.WaitGroup
wg2 := wg),否则后续 Done() 作用于副本,主 wg 无变化WaitGroup 只保证 goroutine 执行完毕的时机,不保护读写共享内存。例如多个 goroutine 向同一 slice 追加元素,即使用了 WaitGroup,仍可能 panic 或丢数据。
sync.Mutex、sync.RWMutex 或 channels
sync/atomic(如 atomic.AddInt64),比锁更轻量WaitGroup 的真正难点不在语法,而在于它只回答「都做完了吗」,从不回答「做对了吗」——共享状态的正确性,得靠你亲手守住。