waitgroup.add() 必须在goroutine启动前调用,否则会导致计数漏加、wait()提前返回或panic;done()需确保执行(panic时依赖defer wg.done());waitgroup不可复制、不可复用未重置实例;并发写共享数据需额外同步机制。

WaitGroup.Add() 必须在 goroutine 启动前调用
这是最常踩的坑:把 Add() 放在 go 语句内部或之后,会导致计数器漏加、Wait() 提前返回,甚至 panic(当 Done() 被调用次数超过初始计数时)。根本原因是 Add() 和 go 不是原子操作,调度器可能在 Add() 执行前就切走主 goroutine,子 goroutine 已开始执行并调用 Done()。
- 正确写法:先
wg.Add(1),再go func() { ... }() - 循环启动多个 goroutine 时,不能在闭包里调用
wg.Add(1)—— 那会变成所有 goroutine 竞争同一个Add()调用,极大概率漏加 - 若需动态确定数量(比如从 channel 读取任务),必须在启动 goroutine 前完成全部
Add(n),不能边读边加
Done() 必须保证执行,哪怕 panic 发生
如果 goroutine 中发生 panic,而 Done() 没被调用,Wait() 就永远阻塞。这不是“忘记 defer”的问题,而是 panic 会跳过普通 defer;必须用 recover + 显式 Done(),或确保 defer wg.Done() 在 panic 路径上仍能触发。
-
defer wg.Done()是安全的 —— 它在函数退出时执行,包括 panic 导致的退出 - 但若在 goroutine 内部手动调用
wg.Done()(没 defer),panic 会跳过它,导致计数器卡住 - 不要在
Done()后再做耗时或可能 panic 的操作,否则Wait()虽已唤醒,但逻辑已错乱
WaitGroup 不可复制,且不能复用未重置的状态
sync.WaitGroup 包含 noCopy 字段,运行时检测到值拷贝(如作为函数参数传值、赋给新变量)会触发 go vet 报告;更隐蔽的问题是复用已 Wait() 过的实例:计数器归零后再次 Add() 是允许的,但必须确保没有其他 goroutine 正在调用 Wait() 或 Done(),否则出现竞态。
- 禁止:将
wg作为参数传值 —— 应传指针*sync.WaitGroup - 禁止:在
Wait()返回后直接用于下一轮,除非你明确控制了所有 goroutine 已退出且无残留调用 - 安全复用方式:定义为局部变量,每次并发任务新建一个
wg;或重用时确保前一轮彻底结束(比如用sync.Once控制初始化)
收集结果时避免 data race,别共享 slice 或 map
并发收集器常见需求是把每个 goroutine 的结果存进公共容器(如 []int 或 map[string]int),但这些类型非并发安全。WaitGroup 只管同步,不管数据安全 —— 错误地直接 append 或写 map 会导致 panic 或数据损坏。
- 推荐方案:每个 goroutine 写入独立 channel,主 goroutine 从 channel 读取并聚合
- 若必须用 slice,用
sync.Mutex保护append操作,或预先分配好容量,用索引写入(需提前知道长度) - map 写入必须配
sync.RWMutex,读多写少场景可用sync.Map,但注意其 API 与原生 map 不同(如不支持 range)
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











