在Go语言并发编程中,我们经常需要等待多个 goroutine 执行完毕后再继续下一步操作。Go 提供的
sync.WaitGroup
就是专为这种**“等待一组任务完成”**而设计的同步原语。
一、基本原理
sync.WaitGroup
提供三个主要方法:
方法 | 说明 |
Add(n int) |
设置等待的 goroutine 数量(加计数) |
Done() |
每个 goroutine 完成时调用(减计数) |
Wait() |
阻塞主 goroutine,直到计数归零 |
它内部使用计数器+条件变量,当所有 goroutine 都调用 Done()
后,Wait()
才会解除阻塞。
二、典型用法
示例:等待 10 个 goroutine 执行完毕
package main import ( "fmt" "sync" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 每个 goroutine 完成时调用 fmt.Printf("Worker %d is working...\n", id) // 模拟工作 } func main() { var wg sync.WaitGroup for i := 1; i <= 10; i++ { wg.Add(1) // 每启动一个 goroutine,加1 go worker(i, &wg) } wg.Wait() // 阻塞直到所有 goroutine 完成 fmt.Println("All workers done.") }
输出示意:
Worker 1 is working... Worker 2 is working... ... All workers done.
三、常见错误及注意事项
1. Add()
必须在 goroutine 启动前调用
错误示例:
go func() { wg.Add(1) // 此时 goroutine 可能已开始执行,竞态风险 ... }()
正确做法:
wg.Add(1) go func() { ... wg.Done() }()
2. 不可重复使用已完成的 WaitGroup(没有“重置”功能)
WaitGroup 设计为一次性同步器,不建议重复使用,若确需控制并发次数,可用 sync.Pool
或 semaphore
替代。
四、结合匿名函数使用
for i := 0; i < 5; i++ { wg.Add(1) go func(i int) { defer wg.Done() fmt.Println("i:", i) }(i) } wg.Wait()
⚠️ 注意:传参 i
必须显式传入闭包,避免捕获变量陷阱。
五、使用场景
- • 等待一组任务执行完成(如:并发下载、批量计算)
- • 控制主函数在 goroutine 完成后再退出
- • 可搭配 Channel 和 Context 使用,实现更复杂的并发控制模型
六、小结
- •
sync.WaitGroup
是 Go 并发编程中最常用的同步工具之一。 - • 使用
Add
/Done
/Wait
实现多协程间的同步等待。 - • 使用时避免竞态和变量捕获问题。