wg.Add()放在哪里

var wg sync.WaitGroup
var v int32 = 0 
for i = 0; i < 100; i++{
   go func(){
       wg.Add(1) // wrong place
       atomic.AddInt32(&v,1)
       wg.Done()
   } 
}
wg.Wait()
fmt.Println(v)

这是我从这个视频中看到的一段代码https://subscription.packtpub.com/video/application_development/9781788994880/97598/97608/goroutines

v总是小于100,我认为原因可能是wg.Wait()因为我们放入wg.Add(1)匿名函数并且在同一个 goroutinewg.Done()中将被立即调用,因此 main goroutine 从阻塞状态恢复执行。

但是如果我们将wg.Add(1)放入 for 循环中, v 将始终为100

var wg sync.WaitGroup
var v int32 = 0 
for i = 0; i < 100; i++{
   wg.Add(1)
   go func(){
       atomic.AddInt32(&v,1)
       wg.Done()
   } 
}
wg.Wait()
fmt.Println(v)

我的问题是为什么我们可以保证 main goroutine 总是在这里阻塞,这样 v 最终会等于 100。如果在 for 循环之前将一个任务添加到wg,并且主 goroutine 在这里恢复执行,因为此时没有任务,这是否可能。

回答

您应该始终wg.Add()在启动将调用的 goroutine 之前调用wg.Done()

在您更正的示例中,maingoroutine 只能wg.Wait()for循环之后到达,这保证您调用wg.Add()一百次,因此wg.Wait()将阻塞直到wg.Done()被调用100次数。

wg.Add()调用在新的 goroutine 中时,不能保证任何wg.Add()调用都会在maingoroutine 到达之前执行,wg.Wait()因为它们是并发运行的(直到此时没有同步)。在这种情况下的行为是不确定的(取决于 goroutine 调度程序,它在没有显式同步的情况下是不确定的)。

请注意,如果您知道您的循环会进行100迭代,则另一种选择是wg.Add(100)在循环之前调用。我建议不要使用这种做法,因为这需要在循环包含breakcontinue操作时小心,这可能会导致启动的maingoroutine减少,从而最终导致您的goroutine 卡住。是的,在您的情况下,这可能是微不足道的,但如果此代码及时发展,它可能会变得不那么明显,并可能导致未来的错误。当场景中涉及启动 goroutines 时,说它更快是无关紧要的。如果您只wg.Add(1)在启动 goroutine 之前调用过,那么以后是否有条件地跳过这部分也没有关系,因为您将在wg.Add()启动 goroutine 的同时跳过,并且您的代码将保持正确。

使用时要遵循的简单“规则” sync.WaitGroup:(引自此答案)

  • WaitGroup.Add()go语句之前调用“原始”goroutine(启动一个新的)
  • 建议调用WaitGroup.Done()deferred,因此即使 goroutine panic 也会被调用
  • 如果你想传递WaitGroup给其他函数(而不是使用包级变量),你必须传递一个指向它的指针,否则WaitGroup(这是一个结构)将被复制,并且Done()不会观察到在副本上调用的方法在原来的

以上是wg.Add()放在哪里的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>