在Go语言并发编程中,我们经常需要等待多个 goroutine 执行完毕后再继续下一步操作。Go 提供的
sync.WaitGroup
就是专为这种**"等待一组任务完成"**而设计的同步原语。
一、基本原理
sync.WaitGroup
提供三个主要方法:
方法 | 说明 |
---|---|
Add(n int) |
设置等待的 goroutine 数量(加计数) |
Done() |
每个 goroutine 完成时调用(减计数) |
Wait() |
阻塞主 goroutine,直到计数归零 |
它内部使用计数器+条件变量,当所有 goroutine 都调用 Done()
后,Wait()
才会解除阻塞。
二、典型用法
示例:等待 10 个 goroutine 执行完毕
go
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.")
}
输出示意:
erlang
Worker 1 is working...
Worker 2 is working...
...
All workers done.
三、常见错误及注意事项
1. Add()
必须在 goroutine 启动前调用
错误示例:
scss
go func() {
wg.Add(1) // 此时 goroutine 可能已开始执行,竞态风险
...
}()
正确做法:
scss
wg.Add(1)
go func() {
...
wg.Done()
}()
2. 不可重复使用已完成的 WaitGroup(没有"重置"功能)
WaitGroup 设计为一次性同步器,不建议重复使用,若确需控制并发次数,可用 sync.Pool
或 semaphore
替代。
四、结合匿名函数使用
go
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
实现多协程间的同步等待。 - • 使用时避免竞态和变量捕获问题。