插件窝 干货文章 Go语言sync.WaitGroup如何正确使用才能避免并发竞争?

Go语言sync.WaitGroup如何正确使用才能避免并发竞争?

WaitGroup goroutine 调用 使用 816    来源:    2025-03-13

在Go语言中,sync.WaitGroup 用于等待一组 goroutine 完成。为了避免并发竞争(race condition),需要正确使用 sync.WaitGroup。以下是一些关键点,帮助你避免并发竞争:

1. 正确初始化 WaitGroup

在开始使用 WaitGroup 之前,确保它被正确初始化。通常,WaitGroup 不需要显式初始化,因为它是一个结构体,默认值就是可用的。

var wg sync.WaitGroup

2. 在启动 goroutine 之前调用 Add

在启动每个 goroutine 之前,调用 wg.Add(1) 来增加 WaitGroup 的计数器。这样可以确保在 Wait 调用之前,计数器已经正确增加。

wg.Add(1)
go func() {
    defer wg.Done()
    // 你的代码
}()

3. 在 goroutine 结束时调用 Done

在每个 goroutine 结束时,调用 wg.Done() 来减少 WaitGroup 的计数器。通常使用 defer 来确保 Done 一定会被调用。

go func() {
    defer wg.Done()
    // 你的代码
}()

4. 在主 goroutine 中调用 Wait

在主 goroutine 中调用 wg.Wait() 来等待所有 goroutine 完成。Wait 会阻塞,直到 WaitGroup 的计数器归零。

wg.Wait()

5. 避免在 goroutine 中修改 WaitGroup

不要在 goroutine 中修改 WaitGroup 的计数器(即不要在 goroutine 中调用 AddDone),除非你非常清楚自己在做什么。通常,Add 应该在主 goroutine 中调用,而 Done 应该在每个 goroutine 结束时调用。

6. 使用 sync.WaitGroup 的指针

如果你需要在多个函数之间传递 WaitGroup,建议使用指针传递,以避免复制 WaitGroup 实例。

func worker(wg *sync.WaitGroup) {
    defer wg.Done()
    // 你的代码
}

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go worker(&wg)
    wg.Wait()
}

7. 使用 -race 标志检测竞争条件

在开发和测试阶段,使用 -race 标志来编译和运行你的程序,以检测潜在的竞争条件。

go run -race main.go

示例代码

以下是一个完整的示例,展示了如何正确使用 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")
}

总结

  • 在启动 goroutine 之前调用 Add
  • 在 goroutine 结束时调用 Done
  • 在主 goroutine 中调用 Wait
  • 避免在 goroutine 中修改 WaitGroup 的计数器。
  • 使用 -race 标志检测竞争条件。

通过遵循这些规则,你可以有效地避免并发竞争,并正确使用 sync.WaitGroup