在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
的正确使用。