同步示例。 WaitGroup 正确吗?

这个 sync.WaitGroup的例子用法正确吗?它给出了预期的结果,但我不确定的 wg.Add(4)和位置的 wg.Done()。用 wg.Add()同时添加四个 goroutine 有意义吗?

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")
}

结果(如预期) :

Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
92335 次浏览

是的,这个例子是正确的。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")
}

然而,当您已经知道 wg.Add将被调用多少次时,一遍又一遍地调用 wg.Add是毫无意义的。


如果指数跌到零以下,Waitgroups就会恐慌。计数器从零开始,每个 Done()是一个 -1,每个 Add()取决于参数。因此,为了确保计数器从来没有下降到以下,并避免恐慌,你需要的 Add()保证来之前的 Done()

在围棋中,这样的保证是由 内存模型给出的。

内存模型指出,一个 goroutine 中的所有语句似乎都按照写入时的顺序执行。有可能他们不会按照这个顺序排列,但是结果会像是这样。它还保证一个 goroutine doesn't run until after the go statement that calls it。因为 Add()出现在 go语句之前,而 go语句出现在 Done()之前,所以我们知道 Add()出现在 Done()之前。

如果 go语句出现在 Add()之前,程序可能会正确运行。但是,这将是一个竞态条件,因为它不能得到保证。

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

As Stephen Weinberg points out in 他对这个问题的回答, you do have to increment the waitgroup 副院长 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()
}()
}

然后,您可以不使用 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_

  • 基于飞蛾答案的小改进
  • 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")
}