sync.WaitGroup goroutine 启动前的初始化

sync.WaitGroup initialization before goroutine start

我有以下代码作为测试的一部分:

    expected := 10
    var wg sync.WaitGroup
    for i := 0; i < expected; i++ {
        go func(wg *sync.WaitGroup) {
            wg.Add(1)
            defer wg.Done()
            // do something
        }(&wg)
    }
    wg.Wait()

令我惊讶的是,当 运行 进行“测试”时,我得到了 panic: Fail in goroutine after TestReadWrite has completed。当 运行 "go test -race" 时,我没有感到恐慌,但后来测试失败了。在这两种情况下,尽管有一个 wg.Wait(),一个 goroutine 并没有完成执行。

我进行了以下更改,现在测试按预期工作:

    expected := 10
    var wg sync.WaitGroup
    wg.Add(expected)
    for i := 0; i < expected; i++ {
        go func(wg *sync.WaitGroup) {
            defer wg.Done()
            // do something
        }(&wg)
    }
    wg.Wait()

我的疑惑是:

  1. 到目前为止,我看到的很多代码都在 goroutine 中执行 wg.Add(1)。为什么在这种特定情况下它会出现意外行为?这里似乎发生的是,一些 goroutine 似乎完成 运行ning,并在其他 goroutine 甚至开始 运行 之前通过 wg.Wait()。在 goroutine 中使用 wg.Add(1) 是危险的还是要避免的?如果这不是一般的问题,那么究竟是什么导致了这里的问题?
  2. 添加 wg.Add(expected) 是解决此问题的正确方法吗?

根据docs-

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

因此 Add() 必须由启动其他 goroutine 的 goroutine 调用,在您的情况下是 main goroutine。

在第一个代码片段中,您在其他 goroutines 中调用 Add() 而不是导致问题的 main goroutine -

expected := 10
var wg sync.WaitGroup
for i := 0; i < expected; i++ {
   go func(wg *sync.WaitGroup) {
       wg.Add(1) // Do not call Add() here
       defer wg.Done()
       // do something
   }(&wg)
}
wg.Wait()

第二个片段有效,因为您在 main goroutine -

中调用 Add()
expected := 10
var wg sync.WaitGroup
wg.Add(expected) // Okay
for i := 0; i < expected; i++ {
    go func(wg *sync.WaitGroup) {
       defer wg.Done()
       // do something
     }(&wg)
}
wg.Wait()

Is adding wg.Add(expected) the correct way to address this problem?

也可以在for循环中调用wg.Add(1)-

expected := 10
var wg sync.WaitGroup
for i := 0; i < expected; i++ {
    wg.Add(1) // Okay
    go func(wg *sync.WaitGroup) {
       defer wg.Done()
       // do something
     }(&wg)
}
wg.Wait()

您的第一个方法出现恐慌,因为 (WaitGroup.Add):

Add adds delta, which may be negative, to the WaitGroup counter. If the counter becomes zero, all goroutines blocked on Wait are released. If the counter goes negative, Add panics.

...

...

Typically this means the calls to Add should execute before the statement creating the goroutine or other event to be waited for

当在代码末尾调用 Wait() 时 none goroutines 可能已经开始执行 - 因此 WaitGroup 中保存的值为 0。稍后当您的 go-routine 执行调用时go-routine 已经发布了。这将导致意想不到的行为,在您的情况下会引起恐慌。也许你在那里使用了调用 go-routine 的值。

你的第二种方法绝对没问题。您也可以在循环内调用 .Add(1) - 但在 go func 块外