113

这个示例用法sync.WaitGroup正确吗?它给出了预期的结果,但我不确定wg.Add(4)和 的位置wg.Done()。一次添加四个 goroutine 有意义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")
}

结果(如预期):

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

3 回答 3

156

是的,这个例子是正确的。重要的是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()都是 a-1并且每个都Add()取决于参数。因此,为确保计数器永远不会低于并避免恐慌,您Add()需要保证Done().

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

内存模型指出,单个 goroutine 中的所有语句似乎都以与它们编写时相同的顺序执行。他们可能实际上不会按照那个顺序,但结果会好像是那样。还保证goroutine 在调用它的语句之后才会运行go。由于Add()发生在go语句之前且go语句发生在 之前Done(),我们知道Add()发生在 之前Done()

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

于 2013-10-06T12:34:41.983 回答
29

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

正如斯蒂芬温伯格在他对这个问题的回答中指出的那样,您必须在生成 gofunc之前增加等待组,但您可以通过将 gofunc spawn 包装在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_

于 2015-09-27T15:26:22.060 回答
24
  • 基于 Mroth 答案的小改进
  • 使用 defer for 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("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")
}
于 2018-07-17T17:39:53.023 回答