插件窝 干货文章 Go语言sync.WaitGroup计数机制:如何避免Wait方法提前结束?

Go语言sync.WaitGroup计数机制:如何避免Wait方法提前结束?

调用 goroutine Done 方法 555    来源:    2025-03-15

在Go语言中,sync.WaitGroup 用于等待一组goroutine完成执行。WaitGroup 通过内部的计数器来跟踪正在执行的goroutine数量。Add 方法增加计数器,Done 方法减少计数器,而 Wait 方法会阻塞,直到计数器归零。

为了避免 Wait 方法提前结束,你需要确保以下几点:

1. 正确使用 AddDone

  • Add 方法:在启动每个goroutine之前调用 Add 方法,增加计数器。通常是在主goroutine中调用 Add
  • Done 方法:在每个goroutine完成时调用 Done 方法,减少计数器。通常是在goroutine的末尾使用 defer wg.Done() 来确保 Done 一定会被调用。

2. 确保 AddDone 的调用次数匹配

  • 每次调用 Add 方法增加计数器时,必须确保有对应的 Done 方法调用。如果 Add 的调用次数多于 DoneWait 方法将永远不会返回。
  • 如果 Done 的调用次数多于 Add,可能会导致 WaitGroup 的计数器变为负数,从而引发 panic。

3. 避免在goroutine中调用 Add

  • 不要在goroutine内部调用 Add,因为这可能导致 Wait 方法在 Add 被调用之前就已经返回。Add 方法应该在主goroutine中调用,以确保计数器在goroutine启动之前就已经增加。

4. 使用 defer 确保 Done 被调用

  • 在每个goroutine中使用 defer wg.Done() 来确保 Done 方法在goroutine结束时一定会被调用,即使goroutine中发生了 panic。

示例代码

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 确保 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")
}

总结

  • 确保 AddDone 的调用次数匹配。
  • 在主goroutine中调用 Add,在goroutine中使用 defer wg.Done()
  • 避免在goroutine中调用 Add

通过遵循这些规则,你可以避免 Wait 方法提前结束,确保程序正确等待所有goroutine完成。