sync.WaitGroup的示例是否正确?

时间:2013-10-06 12:10:39

标签: go goroutine

这个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

3 个答案:

答案 0 :(得分:135)

是的,这个例子是正确的。重要的是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是毫无意义的。


如果计数器低于零,

Waitgroups恐慌。计数器从零开始,每个Done()-1,每个Add()取决于参数。因此,为了确保计数器永远不会降到最低点并避免恐慌,您需要Add() 保证来到Done()之前。

在Go中,此类保证由memory model提供。

内存模型声明单个goroutine中的所有语句似乎都以与写入时相同的顺序执行。它们可能实际上不是那个顺序,但结果就好像它一样。它也保证goroutine doesn't run until after the go statement that calls it。由于Add()发生在go语句之前,而go语句发生在Done()之前,我们知道Add()发生在Done()之前。

如果您要在go之前发出Add()声明,该程序可能会正常运行。但是,这将是一种竞争条件,因为它不会得到保证。

答案 1 :(得分:24)

我建议将wg.Add()来电嵌入到doSomething()功能本身,这样如果您调整其被呼叫的次数,则无需单独调整手动添加参数可能会导致错误,如果你更新一个但忘记更新另一个(在这个不太可能的例子中,但我个人认为这是更好的代码重用实践)。

正如Stephen Weinberg在his answer to this question中指出的那样,你必须在等待组之前增加来产生gofunc,但你可以通过将gofunc spawn包装在{{{}中来实现这一点。 1}}函数本身,像这样:

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

作为一个游乐场:http://play.golang.org/p/WZcprjpHa_

答案 2 :(得分:12)

  • 基于Mroth答案的小改进
  • 使用defer进行更安全
  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")
}