不同的wg.Done()导致WaitGroup在前一个Wait返回之前被重用。

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

Different wg.Done() causes WaitGroup is reused before previous Wait has returned

问题

我正在测试sync.WaitGroup,如果我将defer wg.Done()放在函数的开头,像这样:

package main

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

func worker(wg *sync.WaitGroup, id int) error {
    defer wg.Done() // 放在这里会导致错误
	fmt.Printf("Worker %v: Finished\n", id)
    if true {
        return nil
    }

    return nil
}

var wg sync.WaitGroup // 我应该将`wg`放在函数外部
func callWorker(i int){
    fmt.Println("Main: Starting worker", i)
    fmt.Printf("Worker %v: Finished\n", id)
	wg.Add(1)
	go worker(&wg, i)
    wg.Wait()
}

func main() {
	for i := 0; i < 1000; i++ {
        go callWorker(i)
    }
    time.Sleep(time.Second * 60)
	fmt.Println("Main: Waiting for workers to finish")

	fmt.Println("Main: Completed")
}

在某些情况下,我会得到WaitGroup is reused before previous Wait has returned的错误,就像这样:

不同的wg.Done()导致WaitGroup在前一个Wait返回之前被重用。

但是,如果我将defer wg.Done()放在函数的末尾,它就会成功运行,为什么呢?

func worker(wg *sync.WaitGroup, id int) error {
    
    fmt.Printf("Worker %v: Finished\n", id)
    if true {
        return nil
    }
    defer wg.Done() // 放在这里,没问题
    return nil
}
英文:

I am testing sync.WaitGroup, if I put defer wg.Done() in the begining of the function, like this:

package main

import (
		&quot;fmt&quot;
		&quot;sync&quot;
		&quot;time&quot;
)

func worker(wg *sync.WaitGroup, id int) error {
    defer wg.Done() // put here cause error
	fmt.Printf(&quot;Worker %v: Finished\n&quot;, id)
    if true {
        return nil
    }

    return nil
}

var wg sync.WaitGroup // I should put `wg` outside of this function
func callWorker(i int){
    fmt.Println(&quot;Main: Starting worker&quot;, i)
    fmt.Printf(&quot;Worker %v: Finished\n&quot;, id)
	wg.Add(1)
	go worker(&amp;wg, i)
    wg.Wait()
}

func main() {
	for i := 0; i &lt; 1000; i++ {
        go callWorker(i)
    }
    time.Sleep(time.Second * 60)
	fmt.Println(&quot;Main: Waiting for workers to finish&quot;)

	fmt.Println(&quot;Main: Completed&quot;)
}

I will get WaitGroup is reused before previous Wait has returned in some cases, like this

不同的wg.Done()导致WaitGroup在前一个Wait返回之前被重用。

but if I put defer wg.Done() in the end of function, it runs successfully, why?

func worker(wg *sync.WaitGroup, id int) error {
    
    fmt.Printf(&quot;Worker %v: Finished\n&quot;, id)
    if true {
        return nil
    }
    defer wg.Done() // put here, it is ok
    return nil
}

答案1

得分: 3

文档中指出:“如果一个WaitGroup被重复使用来等待多个独立的事件集,那么新的Add调用必须在所有先前的Wait调用返回之后发生”。

你在一些goroutine中调用了wg.Done(),然后才调用其他goroutine中的wg.Add(1),这是不允许的,正如文档所述。你需要在启动所有这些goroutine之前调用wg.Add,而且你可能只需要调用一次wg.Add(1000)

你其他代码能够正常工作的原因是它从未调用过wg.Done(),你有以下代码:

if true {
     return nil
}
defer wg.Done()

所以你总是在没有到达defer语句的情况下返回,因此从未调用过wg.Done()

请按照以下方式修改代码:

func callWorker(i int){
    fmt.Println("Main: Starting worker", i)
    // 在这里不能调用Add,因为在其他goroutine中已经调用了Done
    go worker(&wg, i)
    wg.Wait()
}

func main() {
    wg.Add(1000) // <---- 在任何goroutine中调用Done之前,必须先调用Add
    for i := 0; i < 1000; i++ {
        go callWorker(i)
    }
    time.Sleep(time.Second * 60)
    fmt.Println("Main: Completed")
}
英文:

The docs state that "If a WaitGroup is reused to wait for several independent sets of events, new Add calls must happen after all previous Wait calls have returned"

You are calling wg.Done() in some goroutines before calling wg.Add(1) in others, which is not allowed, as stated by the docs. You need to call wg.Add before you start all those goroutines, and you might as well just call it once, wg.Add(1000)

The reason your other code works is that it never calls wg.Done(), you have

if true {
     return nil
}
defer wg.Done()

so you always return without reaching the defer statement, so there are never any calls to wg.Done().

Do this:

func callWorker(i int){
    fmt.Println(&quot;Main: Starting worker&quot;, i)
    // you cannot call Add here because Done has been called in other goroutines
    go worker(&amp;wg, i)
    wg.Wait()
}

func main() {
    wg.Add(1000) // &lt;---- You must call Add before Done is called in any goroutine
    for i := 0; i &lt; 1000; i++ {
        go callWorker(i)
    }
    time.Sleep(time.Second * 60)
    fmt.Println(&quot;Main: Completed&quot;)
}

答案2

得分: 0

问题是,如果在等待组上调用Wait(),则不允许重用此等待组并再次调用Add(),直到Wait()调用返回为止(参见文档)。在此程序中,callWorker函数本身是一个goroutine,并且所有callWorker函数都在并发运行。它们互不等待地尝试调用Add(),而前一个Wait()调用尚未完成。

前几个worker在没有错误的情况下产生结果,因为它们的Wait()调用恰好在下一个Add()调用之前返回,这是一个典型的竞态条件。

如果要让worker并发运行,必须将Wait()Add()移出callWorker函数。在for循环之后调用Wait()。并且应该在循环内的callWorker之前调用Add(),否则程序将在callWorker有机会向等待组添加内容之前完成,因此Wait()没有等待的内容。在main()中也不需要time.Sleep。

func main() {
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go callWorker(i)
    }
    wg.Wait()
    fmt.Println("Main: Waiting for workers to finish")

    fmt.Println("Main: Completed")
}
英文:

The problem is that if Wait() is called on a waitgroup, it is not allowed to reuse this waitgroup and call Add() on it again until the Wait() call is returned (see docs). In this programm the callWorker function is itself a go routine and all callWorker functions are running concurrently. Without waiting for each other they try to call Add() while a previous Wait() call isn't finished.

The first workers yield results without error because their Wait() call is luckily returned before the next Add() call, a classical race condition.

If you want the workers to run concurrently you have to move Wait() and Add() out of the callWorker function. Wait() must be called after the for-loop. And Add() should be called inside the loop before callWorker, otherwise the program will finish before callWorker has a chance to add something to the waitgroup and hence Wait() has nothing to wait for. There is no need for time.Sleep in main(), too.

func main() {
    for i := 0; i &lt; 1000; i++ {
    	wg.Add(1)
        go callWorker(i)
    }
    wg.Wait()
    fmt.Println(&quot;Main: Waiting for workers to finish&quot;)

    fmt.Println(&quot;Main: Completed&quot;)
}

huangapple
  • 本文由 发表于 2021年9月18日 11:18:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/69231305.html
匿名

发表评论

匿名网友

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

确定