sync.WaitGroup的示例是否正确?

top*_*kip 102 go goroutine

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

结果(如预期):

Function in background, duration: 150ms
Function in background, duration: 200ms
Function in background, duration: 400ms
Function in background, duration: 600ms
Done
Run Code Online (Sandbox Code Playgroud)

Ste*_*erg 148

是的,这个例子是正确的.重要的是,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")
}
Run Code Online (Sandbox Code Playgroud)

但是,wg.Add当你已经知道它会被调用多少次时,反复调用是没有意义的.


Waitgroups如果计数器低于零,则会出现恐慌.计数器从零开始,每个Done()都是a -1,每个都Add()取决于参数.所以,为了保证计数器不会低于,避免恐慌,需要Add()进行担保来之前Done().

在Go中,这种保证由存储器模型给出.

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

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

  • 我对这个问题有一个疑问:"推迟wg.Done()"这样做是不是更好?这样我们就可以确定它被调用而不管goroutine采用什么路由?谢谢. (9认同)
  • 如果你纯粹想要确保在所有的例行程序完成之前函数没有返回那么肯定是推迟.通常,等待组的整个过程就是等到所有工作完成后再对你正在等待的结果做一些事情. (2认同)

mro*_*oth 26

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

正如Stephen Weinberg在回答这个问题时指出的那样,你必须在产生gofunc 之前增加waitgroup,但是你可以通过将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()
    }()
}
Run Code Online (Sandbox Code Playgroud)

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

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

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


小智 15

  • 基于先生回答的小改进
  • 使用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")
}
Run Code Online (Sandbox Code Playgroud)