sync.WaitGroup的正确用法示例是什么?

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

Example for sync.WaitGroup correct?

问题

这是sync.WaitGroup的正确使用示例吗?它给出了预期的结果,但我对wg.Add(4)wg.Done()的位置不确定。使用wg.Add()一次性添加四个goroutine是否有意义?

package main

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

func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
	duration := millisecs * time.Millisecond
	time.Sleep(duration)
	fmt.Println("Function in background, duration:", duration)
	wg.Done()
}

func main() {
	var wg sync.WaitGroup
	wg.Add(4)
	go dosomething(200, &wg)
	go dosomething(400, &wg)
	go dosomething(150, &wg)
	go dosomething(600, &wg)

	wg.Wait()
	fmt.Println("Done")
}

结果(如预期):

Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
英文:

Is this example usage of sync.WaitGroup correct? It gives the expected result, but I am unsure about the wg.Add(4) and the position of wg.Done(). Does it make sense to add the four goroutines at once with wg.Add()?

http://play.golang.org/p/ecvYHiie0P

package main

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

func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
	duration := millisecs * time.Millisecond
	time.Sleep(duration)
	fmt.Println("Function in background, duration:", duration)
	wg.Done()
}

func main() {
	var wg sync.WaitGroup
	wg.Add(4)
	go dosomething(200, &wg)
	go dosomething(400, &wg)
	go dosomething(150, &wg)
	go dosomething(600, &wg)

	wg.Wait()
	fmt.Println("Done")
}

Result (as expected):

Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done

答案1

得分: 157

是的,这个例子是正确的。重要的是wg.Add()发生在go语句之前,以防止竞态条件。以下的代码也是正确的:

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go dosomething(200, &wg)
    wg.Add(1)
    go dosomething(400, &wg)
    wg.Add(1)
    go dosomething(150, &wg)
    wg.Add(1)
    go dosomething(600, &wg)

    wg.Wait()
    fmt.Println("Done")
}

然而,当你已经知道Add()将被调用多少次时,反复调用wg.Add是没有意义的。

WaitGroup在计数器低于零时会引发panic。计数器从零开始,每个Done()是一个-1,每个Add()取决于参数。因此,为了确保计数器永远不会降到零以下并避免panic,你需要确保Add()Done()之前保证发生。

在Go中,这样的保证是由内存模型提供的。

内存模型规定,单个goroutine中的所有语句看起来是按照它们编写的顺序执行的。实际上它们可能不会按照这个顺序执行,但结果就好像是按照这个顺序执行的。还保证了goroutine在调用它的go语句之后才运行。由于Add()发生在go语句之前,而go语句发生在Done()之前,我们知道Add()发生在Done()之前。

如果你将go语句放在Add()之前,程序可能会正常运行。然而,这将是一种竞态条件,因为不能保证它的正确性。

英文:

Yes, this example is correct. It is important that the wg.Add() happens before the go statement to prevent race conditions. The following would also be correct:

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go dosomething(200, &wg)
    wg.Add(1)
    go dosomething(400, &wg)
    wg.Add(1)
    go dosomething(150, &wg)
    wg.Add(1)
    go dosomething(600, &wg)

    wg.Wait()
    fmt.Println("Done")
}

However, it is rather pointless to call wg.Add over and over again when you already know how many times it will be called.


Waitgroups panic if the counter falls below zero. The counter starts at zero, each Done() is a -1 and each Add() depends on the parameter. So, to ensure that the counter never drops below and avoid panics, you need the Add() to be guaranteed to come before the Done().

In Go, such guarantees are given by the memory model.

The memory model states that all statements in a single goroutine appear to be executed in the same order as they are written. It is possible that they won't actually be in that order, but the outcome will be as if it was. It is also guaranteed that a goroutine doesn't run until after the go statement that calls it. Since the Add() occurs before the go statement and the go statement occurs before the Done(), we know the Add() occurs before the Done().

If you were to have the go statement come before the Add(), the program may operate correctly. However, it would be a race condition because it would not be guaranteed.

答案2

得分: 30

我建议将wg.Add()调用嵌入到doSomething()函数本身中,这样如果你调整了它的调用次数,就不必手动单独调整添加参数,否则如果你更新了一个但忘记更新另一个,可能会导致错误(在这个简单的示例中可能不太可能发生,但我个人认为这是更好的代码重用实践)。

正如Stephen Weinberg在他对这个问题的回答中指出的那样,你必须在生成gofunc之前增加waitgroup的计数,但你可以通过将gofunc生成包装在doSomething()函数本身中来轻松实现这一点,就像这样:

func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
    wg.Add(1)
    go func() {
        duration := millisecs * time.Millisecond
        time.Sleep(duration)
        fmt.Println("Function in background, duration:", duration)
        wg.Done()
    }()
}

然后你可以在不使用go调用的情况下调用它,例如:

func main() {
    var wg sync.WaitGroup
    dosomething(200, &wg)
    dosomething(400, &wg)
    dosomething(150, &wg)
    dosomething(600, &wg)
    wg.Wait()
    fmt.Println("Done")
}

作为一个示例:http://play.golang.org/p/WZcprjpHa_

英文:

I would recommend embeding the wg.Add() call into the doSomething() function itself, so that if you adjust the number of times it's called, you don't have to separately adjust the add parameter manually which could lead to an error if you update one but forget to update the other (in this trivial example that is unlikely, but still, I personally believe it to be better practice for code re-use).

As Stephen Weinberg points out in his answer to this question, you do have to increment the waitgroup prior to spawning the gofunc, but you can accomplish this easily by wrapping the gofunc spawn inside the doSomething() function itself, like this:

func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
    wg.Add(1)
    go func() {
	    duration := millisecs * time.Millisecond
	    time.Sleep(duration)
	    fmt.Println("Function in background, duration:", duration)
	    wg.Done()
    }()
}

Then you can call it without the go invocation, e.g.:

func main() {
    var wg sync.WaitGroup
    dosomething(200, &wg)
    dosomething(400, &wg)
    dosomething(150, &wg)
    dosomething(600, &wg)
    wg.Wait()
    fmt.Println("Done")
}

As a playground: http://play.golang.org/p/WZcprjpHa_

答案3

得分: 24

  • 在Mroth的答案基础上进行了小的改进
  • 使用defer来确保Done的安全性
func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
    wg.Add(1)
    go func() {
        defer wg.Done()
        duration := millisecs * time.Millisecond
        time.Sleep(duration)
        fmt.Println("后台函数,持续时间:", duration)
    }()
}

func main() {
    var wg sync.WaitGroup
    dosomething(200, &wg)
    dosomething(400, &wg)
    dosomething(150, &wg)
    dosomething(600, &wg)
    wg.Wait()
    fmt.Println("完成")
}
英文:
  • small improvement based on Mroth answer
  • using defer for Done is safer

> func dosomething(millisecs time.Duration, wg *sync.WaitGroup) {
> wg.Add(1)
> go func() {
> defer wg.Done()
> duration := millisecs * time.Millisecond
> time.Sleep(duration)
> fmt.Println("Function in background, duration:", duration)
> }()
> }
>
> func main() {
> var wg sync.WaitGroup
> dosomething(200, &wg)
> dosomething(400, &wg)
> dosomething(150, &wg)
> dosomething(600, &wg)
> wg.Wait()
> fmt.Println("Done")
> }

huangapple
  • 本文由 发表于 2013年10月6日 20:10:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/19208725.html
匿名

发表评论

匿名网友

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

确定