英文:
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()
我的疑问是:
- 到目前为止,我看到的很多代码在goroutine内部执行
wg.Add(1)
。为什么在这种特定情况下会出现意外行为?这里似乎发生的情况是,某些goroutine似乎在其他goroutine开始运行之前就完成了运行,并且跳过了wg.Wait()。在goroutine内部使用wg.Add(1)是否是危险的/应该避免的?如果这在一般情况下不是问题,那么到底是什么导致了这个问题? - 添加
wg.Add(expected)
是解决这个问题的正确方法吗?
英文:
I had the following code as part of a test:
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()
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 < expected; i++ {
go func(wg *sync.WaitGroup) {
defer wg.Done()
// do something
}(&wg)
}
wg.Wait()
My doubts are:
- 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? - 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 < expected; i++ {
go func(wg *sync.WaitGroup) {
wg.Add(1) // Do not call Add() here
defer wg.Done()
// do something
}(&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 < 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?
You can also call wg.Add(1)
in the for loop -
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()
答案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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论