我有一个代码,其中单个goroutine将触发不确定数量的子goroutine,而后者又将触发更多的goroutine,依此类推。我的目标是等待所有子goroutine完成。
我不知道我将要预先触发的goroutine的总数,所以我不能使用sync.WaitGroup,理想情况下,我不必人为地限制通过channel- as- semaphore模式运行的goroutine的总数。
简短地讲,我想到在每个goroutine中都有一个本地通道或等待组,以作为等待其所有子级的信号灯,但是这导致每个goroutine在所有后代完成时都在占用堆栈空间附近徘徊。
现在,我的想法是在触发goroutine时增加一个原子计数器(在父级中,以避免在父级完成后孩子开始运行时避免虚假地将其设为零),在goroutine完成时将其递减,并定期检查它是否相等归零。
我基本上是在正确的轨道上,还是有一个更优雅的解决方案?
我编写了的第一个实现sync.WaitGroup,并且很好地支持了这种情况和其他极端情况。从那以后,德米特里(Dmitry)改进了实施方式,鉴于他的往绩,我敢打赌他只会使它更安全。
sync.WaitGroup
特别是,您可以相信,如果当前有一个或多个被阻止的Wait呼叫,然后在呼叫Add之前以正增量进行呼叫Done,则不会取消阻止任何先前存在的Wait呼叫。
Wait
Add
Done
因此,您绝对可以这样做,例如:
var wg sync.WaitGroup wg.Add(1) go func() { wg.Add(1) go func() { wg.Done() }() wg.Done() }() wg.Wait()
自从代码首次集成以来,我实际上在生产中使用了等效逻辑。
作为参考,此内部注释已在第一个实现中提出,并且仍然存在:
// WaitGroup creates a new semaphore each time the old semaphore // is released. This is to avoid the following race: // // G1: Add(1) // G1: go G2() // G1: Wait() // Context switch after Unlock() and before Semacquire(). // G2: Done() // Release semaphore: sema == 1, waiters == 0. G1 doesn't run yet. // G3: Wait() // Finds counter == 0, waiters == 0, doesn't block. // G3: Add(1) // Makes counter == 1, waiters == 0. // G3: go G4() // G3: Wait() // G1 still hasn't run, G3 finds sema == 1, unblocked! Bug.
这描述了在接触实现时要记住的另一种竞争条件,但请注意,即使使用G1进行Add(1) + go f()竞争,该模式也是如此G3。
G1
Add(1) + go f()
G3
不过,我理解您的问题,因为最近发布的文档中确实有一个令人困惑的陈述,但是让我们查看评论的历史以了解其实际解决的内容。
Russ在修订版15683中发表了评论:
(...) +// Note that calls with positive delta must happen before the call to Wait, +// or else Wait may wait for too small a group. Typically this means the calls +// to Add should execute before the statement creating the goroutine or +// other event to be waited for. See the WaitGroup example. func (wg *WaitGroup) Add(delta int) {
拉斯的日志注释指出:
同步:注意在哪里打电话(* WaitGroup)。 修复了问题4762。
同步:注意在哪里打电话(* WaitGroup)。
修复了问题4762。
如果阅读问题4762,则会发现:
可能值得在sync.WaitGroup文档中添加明确的注释,即在启动包含对Done的调用的go例程之前,应完成对Add的调用。
因此,该文档实际上是针对这样的代码发出警告:
var wg sync.WaitGroup wg.Add(1) go func() { go func() { wg.Add(1) wg.Done() }() wg.Done() }() wg.Wait()
这确实是坏的。只是应该对评论进行改进,使其更加具体,并避免您在阅读过程中所产生的似是而非但令人误解的理解。