在 goroutine 开始之前进行 sync.WaitGroup 的初始化。

huangapple go评论87阅读模式
英文:

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()
		// 做一些操作
	}(&wg)
}
wg.Wait()

令我惊讶的是,当运行"go test"时,我得到了`panic: Fail in goroutine after TestReadWrite has completed`的错误。当使用"go test -race"运行时,我没有收到panic,但测试在稍后的某个点失败了。在这两种情况下,尽管有wg.Wait(),但某个goroutine没有完成执行。

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

```go
    expected := 10
	var wg sync.WaitGroup
    wg.Add(expected)
	for i := 0; i < expected; i++ {
		go func(wg *sync.WaitGroup) {
			defer wg.Done()
			// 做一些操作
		}(&wg)
	}
	wg.Wait()

我的疑问是:

  1. 到目前为止,我看到的很多代码在goroutine内部执行wg.Add(1)。为什么在这种特定情况下会出现意外行为?这里似乎发生的情况是,某些goroutine似乎在其他goroutine开始运行之前就完成了运行,并且跳过了wg.Wait()。在goroutine内部使用wg.Add(1)是否是危险的/应该避免的?如果这在一般情况下不是问题,那么到底是什么导致了这个问题?
  2. 添加wg.Add(expected)是解决这个问题的正确方法吗?
英文:

I had the following code as part of a test:

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

To my surprise, I got panic: Fail in goroutine after TestReadWrite has completed when running "go test". When running with "go test -race", I did not get a panic, but the test failed at a later point. In both cases, despite having a wg.Wait(), a goroutine did not finish executing.

I made the following change, and now the test works as expected:

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

My doubts are:

  1. A lot of the code I have seen so far does wg.Add(1) inside the goroutine. Why does it behave unexpectedly in this specific case? What seems to be happening here is that some goroutines seem to finish running, and get past the wg.Wait(), before other goroutines even start to run. Is using wg.Add(1) inside the goroutine dangerous / to be avoided? If this is not a problem in general, what exactly is causing the problem here?
  2. Is adding wg.Add(expected) the correct way to address this problem?

答案1

得分: 3

根据文档

WaitGroup用于等待一组goroutine完成。主goroutine调用Add来设置要等待的goroutine数量。然后每个goroutine运行并在完成时调用Done。同时,Wait可以用于阻塞,直到所有goroutine都完成。

因此,在你的情况下,必须由一个启动其他goroutine的goroutine调用Add(),也就是主goroutine。

在第一个代码片段中,你在其他goroutine中调用了Add(),而不是 goroutine,这导致了问题:

expected := 10
var wg sync.WaitGroup
for i := 0; i < expected; i++ {
   go func(wg *sync.WaitGroup) {
       wg.Add(1) // 不要在这里调用Add()
       defer wg.Done()
       // 做一些事情
   }(&wg)
}
wg.Wait()

第二个代码片段是有效的,因为你在 goroutine中调用了Add():

expected := 10
var wg sync.WaitGroup
wg.Add(expected) // 可以
for i := 0; i < expected; i++ {
    go func(wg *sync.WaitGroup) {
       defer wg.Done()
       // 做一些事情
     }(&wg)
}
wg.Wait()

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

expected := 10
var wg sync.WaitGroup
for i := 0; i < expected; i++ {
    wg.Add(1) // 可以
    go func(wg *sync.WaitGroup) {
       defer wg.Done()
       // 做一些事情
     }(&wg)
}
wg.Wait()
英文:

According to the 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.

So Add() must be called by a goroutine which is initiating other goroutines which in your case is the main goroutine.

In the first code snippet, you are calling Add() inside other goroutines instead of the main goroutine which is causing problem -

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

The second snippet is working because you are calling Add() in the main goroutine -

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

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

You can also call wg.Add(1) in the for loop -

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

答案2

得分: 1

你的第一种方法出现了panic,因为在调用WaitGroup.Add时传入了负数:

> Add方法将delta(可以是负数)添加到WaitGroup计数器中。如果计数器变为零,则释放所有在Wait上阻塞的goroutine。如果计数器变为负数,则Add会引发panic。
>
> ...
>
> ...
>
>通常,这意味着对Add的调用应该在创建goroutine或其他要等待的事件的语句之前执行。

当在代码末尾调用Wait()时,可能还没有任何goroutine开始执行,因此WaitGroup中的值为0。然后,稍后当你的goroutine执行时,调用goroutine已经被释放。这将导致意外的行为,在你的情况下是panic。也许你在其中的goroutine中使用了调用goroutine的值。

你的第二种方法是完全正确的。你也可以在循环内部调用.Add(1),但是在go func块之外。

英文:

Your first approach panics coz (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

When Wait() is called at the end of your code none of the goroutines may have started executing yet - hence the value held in WaitGroup is 0. Then later when your go-routine executes the calling go-routine has already been released. This will lead to unexpected behaviour, a panic in your case. Maybe you were using values from the calling go-routine in there.

Your second approach is absolutely fine. You can also call .Add(1) inside the loop - but outside the go func block

huangapple
  • 本文由 发表于 2021年7月22日 12:29:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/68479198.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定