并发
sync.WaitGroup : 等待一组goroutine执行完毕; 三个主要方法:
- Add(delta int) : 设置等待的goroutine数量;
- Done() : 通知WaitGroup一个goroutine已经完成;
- Wait() : 等待所有goroutine完成;
使用示例
go
func processItems(items []int) {
var wg sync.WaitGroup
// 添加要处理的items数量
wg.Add(len(items))
results := make([]int, len(items))
for i, item := range items {
// 启动处理每个item的goroutine
go func(i, item int) {
defer wg.Done()
// 处理item
results[i] = item * item
fmt.Printf("Processed item %d: %d\n", i, results[i])
}(i, item) // 避免出现闭包的问题
}
// 等待所有处理完成
wg.Wait()
// 处理结果
sum := 0
for _, v := range results {
sum += v
}
fmt.Printf("Sum of processed items: %d\n", sum)
}
常见陷阱和最佳实践
Add 和 Done 必须平衡 确保每次调用 Add(n) 最终都对应 n 次 Done() 调用,否则程序可能: 永久阻塞:如果 Done() 调用次数少于 Add(n) 中的 n 崩溃:如果 Done() 调用次数多于 Add(n) 中的 n(计数器为负时触发 panic)
Add 的调用时机
go
// 错误方式:在goroutine内部调用Add
for i := 0; i < 10; i++ {
go func() {
wg.Add(1) // 错误!可能还没有调用Add就已经调用了Wait
defer wg.Done()
// 工作...
}()
}
wg.Wait() // 可能过早返回
// 正确方式:在启动goroutine前调用Add
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 工作...
}()
}
wg.Wait()
- 不要复制 WaitGroup WaitGroup 是一个结构体,复制它并尝试管理同一个 WaitGroup 的多个副本会导致不确定行为。
go
// 错误方式
func worker(wg sync.WaitGroup) { // wg被复制
defer wg.Done() // 这个Done操作的是复制的wg
// 工作...
}
// 正确方式
func worker(wg *sync.WaitGroup) { // 传递指针
defer wg.Done()
// 工作...
}