插件窝 干货文章 Go语言sync.WaitGroup如何正确使用才能避免并发编程中的等待问题?

Go语言sync.WaitGroup如何正确使用才能避免并发编程中的等待问题?

计数器 goroutine WaitGroup 调用 315    来源:    2025-03-15

在Go语言中,sync.WaitGroup 是一种用于等待一组 goroutine 完成的同步机制。正确使用 sync.WaitGroup 可以避免并发编程中的等待问题。以下是正确使用 sync.WaitGroup 的步骤和注意事项:

1. 初始化 WaitGroup

首先,你需要创建一个 sync.WaitGroup 实例。

var wg sync.WaitGroup

2. 增加计数器

在启动每个 goroutine 之前,调用 Add 方法来增加 WaitGroup 的计数器。计数器表示需要等待的 goroutine 数量。

wg.Add(1) // 增加计数器

3. 启动 goroutine

在启动 goroutine 时,将 WaitGroup 的指针传递给 goroutine,以便在 goroutine 完成时减少计数器。

go func() {
    defer wg.Done() // 减少计数器
    // 执行任务
}()

4. 等待所有 goroutine 完成

在主 goroutine 中,调用 Wait 方法来阻塞,直到所有 goroutine 完成(即计数器归零)。

wg.Wait() // 阻塞,直到计数器归零

5. 完整示例

以下是一个完整的示例,展示了如何正确使用 sync.WaitGroup

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 减少计数器
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second) // 模拟工作
    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() // 等待所有 goroutine 完成
    fmt.Println("All workers done")
}

6. 注意事项

  • 确保 AddDone 的调用次数匹配:每次调用 Add 增加计数器后,必须确保在相应的 goroutine 中调用 Done 来减少计数器。否则,Wait 会一直阻塞。
  • 避免在 goroutine 外部调用 DoneDone 应该在 goroutine 内部调用,以确保在任务完成后减少计数器。
  • 传递 WaitGroup 的指针:在 goroutine 中传递 WaitGroup 时,应该传递指针,而不是值。因为 WaitGroup 是一个结构体,传递值会导致副本的创建,从而无法正确同步。

7. 常见错误

  • 忘记调用 Add:如果忘记调用 AddWait 会立即返回,因为计数器为零。
  • 忘记调用 Done:如果忘记调用 DoneWait 会一直阻塞,因为计数器不会归零。
  • 在 goroutine 外部调用 Done:如果在 goroutine 外部调用 Done,可能会导致计数器减少过早,导致 Wait 提前返回。

通过遵循上述步骤和注意事项,你可以正确使用 sync.WaitGroup 来避免并发编程中的等待问题。