在Go语言并发编程中,我们经常需要等待多个 goroutine 执行完毕后再继续下一步操作。Go 提供的
sync.WaitGroup
就是专为这种**“等待一组任务完成”**而设计的同步原语。
一、基本原理
sync.WaitGroup
提供三个主要方法:
方法 | 说明 |
Add(n int) | 设置等待的 goroutine 数量(加计数) |
Done() | 每个 goroutine 完成时调用(减计数) |
Wait() | 阻塞主 goroutine,直到计数归零 |
它内部使用计数器+条件变量,当所有 goroutine 都调用 Done()
后,Wait()
才会解除阻塞。
二、典型用法
示例:等待 10 个 goroutine 执行完毕
package mainimport ("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.WaitGroupfor i := 1; i <= 10; i++ {wg.Add(1) // 每启动一个 goroutine,加1go 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
实现多协程间的同步等待。 - • 使用时避免竞态和变量捕获问题。