插件窝 干货文章 Go语言sync.WaitGroup的Add(1)究竟该如何使用?

Go语言sync.WaitGroup的Add(1)究竟该如何使用?

goroutine 调用 Add WaitGroup 64    来源:    2025-03-13

sync.WaitGroup 是 Go 语言中用于等待一组 goroutine 完成执行的同步工具。Add(1)WaitGroup 的一个方法,用于增加等待的 goroutine 数量。以下是关于 Add(1) 的使用方法和注意事项:

1. 基本用法

Add(1) 通常在你启动一个新的 goroutine 之前调用,表示你将要等待一个 goroutine 完成。每个 Add(1) 调用都会增加 WaitGroup 的内部计数器。

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 确保在 goroutine 结束时调用 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 <= 3; i++ {
        wg.Add(1) // 增加等待的 goroutine 数量
        go worker(i, &wg)
    }

    wg.Wait() // 等待所有 goroutine 完成
    fmt.Println("All workers done")
}

2. 注意事项

  • Add(1) 的位置Add(1) 应该在启动 goroutine 之前调用,以确保 WaitGroup 的计数器在 goroutine 开始执行之前就已经增加。

  • Done() 的调用:每个 Add(1) 都应该对应一个 Done() 调用,通常在 goroutine 的末尾使用 defer wg.Done() 来确保 Done() 一定会被调用。

  • 避免竞态条件:确保 Add(1)Wait() 的调用是在同一个 goroutine 中进行的,以避免竞态条件。

3. 错误示例

以下是一个错误的示例,展示了 Add(1)Wait() 在不同 goroutine 中调用可能导致的竞态条件:

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 <= 3; i++ {
        go func(i int) {
            wg.Add(1) // 错误的 Add(1) 位置
            worker(i, &wg)
        }(i)
    }

    wg.Wait() // 可能提前返回,因为 Add(1) 可能在 Wait() 之后执行
    fmt.Println("All workers done")
}

在这个错误示例中,Add(1) 在 goroutine 内部调用,可能导致 Wait()Add(1) 之前执行,从而导致 Wait() 提前返回。

4. 总结

  • Add(1) 应该在启动 goroutine 之前调用。
  • 每个 Add(1) 都应该对应一个 Done() 调用。
  • 确保 Add(1)Wait() 的调用是在同一个 goroutine 中进行的,以避免竞态条件。

通过正确使用 Add(1)Done(),你可以确保 WaitGroup 能够正确地等待所有 goroutine 完成。