waitgroup.add() 必须在goroutine启动前调用,正确顺序是先wg.add(1)再go func();done()应使用defer wg.done()确保执行,wait()仅表示计数归零而非goroutine完全退出。

WaitGroup.Add() 必须在 goroutine 启动前调用
很多人写成先 go func() { ... }() 再 wg.Add(1),结果 wg.Wait() 立即返回——因为 Add() 调用太晚,Done() 可能已在 Add() 之前执行完毕,导致计数器变成负数或提前归零。
正确顺序是:先 wg.Add(1),再启动 goroutine。哪怕只启动一个,也必须守这个顺序。
- 如果循环启动多个 goroutine,
wg.Add(n)要放在循环外;若在循环内,必须确保每次迭代都调用一次Add(1) - 切忌在 goroutine 内部调用
wg.Add(1)来“动态补计数”,这会引发竞态(race),go run -race会报fatal error: concurrent map writes或data race -
Add()参数可为负数,但仅应在调用Done()时内部使用;手动传负值极易出错,不推荐
WaitGroup.Done() 和 defer wg.Done() 的实际差异
wg.Done() 等价于 wg.Add(-1),它只是原子减一;真正关键的是调用时机——必须确保它在 goroutine 结束前执行,且仅执行一次。
用 defer wg.Done() 是最稳妥的做法,因为它绑定到函数退出(包括 panic 时),避免因 return 分支多、提前 return 或 panic 导致漏调 Done()。
- 不要写
go func() { defer wg.Done(); ... }()后紧接return,defer 在匿名函数内生效,不是在外层函数里 - 如果 goroutine 执行的是长循环,且中间可能 break/return,仍建议把
defer wg.Done()放在函数开头,而不是塞在每个 exit path 里 - 不能用
wg.Done()替代close(ch)或其他资源清理逻辑,它只管计数,不管状态
WaitGroup 不能被复制,也不能跨 goroutine 传递地址以外的值
Go 中 sync.WaitGroup 是非拷贝类型,结构体里含 noCopy 字段。一旦你把它作为参数值传递、赋值给新变量、或者放进 map/slice 里,go vet 或 -race 会警告 copy of sync.WaitGroup contains copied mutex。
- 始终以指针方式使用:
func worker(wg *sync.WaitGroup, data int),而非func worker(wg sync.WaitGroup, ...) - 不要把
wg塞进 struct 里再传值,除非该 struct 字段声明为*sync.WaitGroup - 常见错误:在 for 循环里写
w := wg; go func(){ w.Done() }()—— 这里w是值拷贝,运行时报错或静默失败
WaitGroup.Wait() 阻塞但不保证 goroutine 已完全退出
wg.Wait() 返回只表示计数器归零,即所有对应的 Done() 已执行;但它不保证那些 goroutine 的栈已回收、内存已释放、或附带的 I/O 已完成。比如 goroutine 里写了文件但没 flush,Wait() 返回后文件可能还没落盘。
- 如果需要强一致性(如写完文件再继续),应在 goroutine 内部做同步(
f.Sync()、close(ch)、显式time.Sleep()等),而不是依赖WaitGroup -
Wait()本身不消耗 CPU,是休眠等待,但若误将Done()漏掉,它会永久阻塞——上线后表现为服务 hang 住,日志停更 - 没有超时机制,如需防卡死,得自己套
select { case ,但注意这并不取消 goroutine,只是放弃等待
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











