Go 中 sync.WaitGroup 与 sync.Mutex 组合可协调 goroutine 生命周期与共享资源访问:WaitGroup 通过 Add/Wait/Done 控制任务启停,Mutex 通过锁机制防止数据竞争,二者分工明确、配合自然。

使用 Go 的 sync.WaitGroup 和 sync.Mutex 组合,能有效协调多个 goroutine 的生命周期与共享资源访问——WaitGroup 负责“等任务完成”,Mutex 负责“防数据竞争”,二者分工明确,配合自然。
WaitGroup 控制任务启停节奏
WaitGroup 适合管理一组并发任务的启动与等待。它不关心任务内部逻辑,只跟踪“谁还没结束”。核心是三个方法:Add()(预设任务数)、Done()(标记完成)、Wait()(阻塞直到全部完成)。
- 必须在启动 goroutine 前调用
Add(1),不能在 goroutine 内部 Add —— 否则可能 Wait 提前返回 -
Done()应该和Add(1)严格配对;推荐用defer wg.Done()避免遗漏 - WaitGroup 本身不是线程安全的:Add 和 Wait 不能并发调用;Add 必须在所有 goroutine 启动前完成
Mutex 保护共享状态不被并发篡改
当多个 goroutine 共同更新一个变量(如计数器、切片、map)时,必须用 Mutex 加锁。它不控制执行顺序,只确保同一时刻最多一个 goroutine 进入临界区。
- 锁的粒度要合适:太粗(如整个函数加锁)会降低并发性;太细(每行都 lock/unlock)易出错且无必要
- 优先使用
mu.Lock()/mu.Unlock()配对,或更稳妥地用defer mu.Unlock() - 避免死锁:不要在持有锁时调用可能再次获取同一把锁的函数;不要在锁内长时间阻塞(如网络请求、time.Sleep)
组合使用:一边等结果,一边安全更新状态
典型场景是并发处理一批数据,并汇总结果(比如统计成功/失败数量、收集返回值)。WaitGroup 确保主协程不提前退出,Mutex 保证汇总操作线程安全。
立即学习“go语言免费学习笔记(深入)”;
示例逻辑:
- 定义全局计数器和互斥锁:
var success, fail int; var mu sync.Mutex - 启动 N 个 goroutine,每个执行任务后根据结果调用
mu.Lock(); success++; mu.Unlock()或类似操作 - 主 goroutine 调用
wg.Wait()后,再安全读取success和fail
注意:不要把 mu.Lock() 放在 WaitGroup 的 Add/Wait 调用路径上——它们无需同步,加锁只会拖慢调度。
替代方案与注意事项
对于简单计数,可考虑 sync/atomic(如 atomic.AddInt64),它比 Mutex 更轻量且无锁;但仅适用于基础类型和有限操作。若需复杂结构(如向切片追加、修改 map),仍需 Mutex。
另外,WaitGroup 无法取消或超时;如有此需求,应结合 context.Context 控制 goroutine 生命周期,WaitGroup 仅用于收尾等待。










