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

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

WaitGroup goroutine 调用 使用    来源:    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 中调用 Add 或 Done),除非你非常清楚自己在做什么。通常,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。