waitgroup.add() 必须在goroutine启动前调用,否则因内存模型无同步保障导致计数器负值panic;需避免循环变量捕获、值传递wg、重复使用及并发写共享数据。

WaitGroup.Add() 必须在 go 语句之前调用
如果把 wg.Add(1) 放进 goroutine 里,wg.Wait() 很可能在任何 Add() 执行前就返回,导致 panic:「sync: negative WaitGroup counter」。这不是偶发问题,而是 Go 内存模型决定的——Add() 和 Wait() 都是非原子操作,没同步保障。
- ✅ 正确顺序:循环中先
wg.Add(1),再go func() { ... }() - ❌ 错误写法:
go func() { wg.Add(1); ... }()或漏掉Add() - 动态任务(比如从 channel 拉取)必须每次拉到一个任务就立刻
wg.Add(1),不能攒着统一加
闭包捕获循环变量导致所有 goroutine 共享同一值
常见错误是直接在 for 循环里用 go func() { process(i) }(),此时所有 goroutine 实际访问的是同一个变量 i 的地址,最终可能全处理最后一个值。
- ✅ 安全做法:显式传参,如
go func(id int) { process(id) }(i) - ✅ 或在循环内定义新变量:
id := i; go func() { process(id) }() - ⚠️ 注意:即使用了
defer wg.Done(),变量捕获错误仍会导致逻辑错乱,不是 panic 但结果不可靠
WaitGroup 不能重复使用,也不能传递值类型
sync.WaitGroup 是值类型,但内部有指针字段和原子操作。如果以值方式传给 goroutine(如 go worker(wg)),每个 goroutine 拿到的是副本,Done() 修改的不是主 wg 的计数器,Wait() 将永远阻塞。
- ✅ 必须传指针:
go worker(&wg) - ❌ 不可复用:一旦
wg.Wait()返回(计数器归零),不能再调用wg.Add()—— 会 panic - 需要多次使用?重声明新变量,或用
var wg sync.WaitGroup在每次任务前初始化
收集并发结果时,WaitGroup 只管生命周期,不负责数据安全
sync.WaitGroup 本身不提供线程安全的数据结构。若多个 goroutine 往同一个 slice 或 map 写结果,必然触发竞态。
- ✅ 推荐组合:用
WaitGroup控制完成时机 +channel收集结果(无缓冲或带足够容量) - ✅ 或用
sync.Mutex保护共享结果容器,但注意锁粒度,别让写操作成为瓶颈 - ⚠️ 常见陷阱:用
append()直接往全局 slice 写,没加锁也没 channel 中转,结果随机丢失或 panic
wg.Add 和 wg.Done,而是当任务数不确定、结果要聚合、还要支持超时或取消时,WaitGroup 得和 context.Context、channel、甚至信号量一起编排——这时候少一个 defer 或多一次 close(),都可能让程序卡死或提前退出。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











