在Go语言中,sync.WaitGroup 用于等待一组 goroutine 完成执行。为了避免并发竞争(race condition),需要正确使用 sync.WaitGroup。以下是一些关键点和最佳实践:
WaitGroup在使用 WaitGroup 之前,必须先初始化它。通常使用 var wg sync.WaitGroup 来声明一个 WaitGroup 变量。
Add在启动每个 goroutine 之前,应该调用 wg.Add(1) 来增加 WaitGroup 的计数器。这样可以确保 WaitGroup 知道有多少个 goroutine 需要等待。
wg.Add(1)
go func() {
defer wg.Done()
// 执行任务
}()
Done在每个 goroutine 结束时,应该调用 wg.Done() 来减少 WaitGroup 的计数器。通常使用 defer wg.Done() 来确保即使 goroutine 发生 panic,Done 也会被调用。
go func() {
defer wg.Done()
// 执行任务
}()
Wait在主 goroutine 中调用 wg.Wait() 来等待所有 goroutine 完成。Wait 会阻塞,直到 WaitGroup 的计数器归零。
wg.Wait()
Add不要在 goroutine 内部调用 wg.Add(1),因为这可能导致竞争条件。Add 应该在主 goroutine 中调用,确保在启动 goroutine 之前计数器已经增加。
sync.WaitGroup 的指针如果 WaitGroup 需要在多个函数之间传递,应该使用指针传递 WaitGroup,而不是值传递。这样可以确保所有 goroutine 操作的是同一个 WaitGroup。
func worker(wg *sync.WaitGroup) {
defer wg.Done()
// 执行任务
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go worker(&wg)
wg.Wait()
}
Done确保每个 Add 调用都有对应的 Done 调用,并且不要重复调用 Done,否则会导致 WaitGroup 的计数器变为负数,从而引发 panic。
sync.WaitGroup 与 defer 结合在 goroutine 中使用 defer wg.Done() 是一个好习惯,因为它可以确保即使 goroutine 发生 panic,Done 也会被调用。
以下是一个完整的示例,展示了如何正确使用 sync.WaitGroup:
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
// 模拟工作
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}
Add。Done。Wait。defer wg.Done() 确保 Done 被调用。Add。WaitGroup。通过遵循这些最佳实践,可以避免并发竞争,并确保 sync.WaitGroup 的正确使用。