一尘不染

等待不确定数量的goroutine

go

我有一个代码,其中单个goroutine将触发不确定数量的子goroutine,而后者又将触发更多的goroutine,依此类推。我的目标是等待所有子goroutine完成。

我不知道我将要预先触发的goroutine的总数,所以我不能使用sync.WaitGroup,理想情况下,我不必人为地限制通过channel-
as-
semaphore
模式运行的goroutine的总数。

简短地讲,我想到在每个goroutine中都有一个本地通道或等待组,以作为等待其所有子级的信号灯,但是这导致每个goroutine在所有后代完成时都在占用堆栈空间附近徘徊。

现在,我的想法是在触发goroutine时增加一个原子计数器(在父级中,以避免在父级完成后孩子开始运行时避免虚假地将其设为零),在goroutine完成时将其递减,并定期检查它是否相等归零。

我基本上是在正确的轨道上,还是有一个更优雅的解决方案?


阅读 238

收藏
2020-07-02

共1个答案

一尘不染

我编写了的第一个实现sync.WaitGroup,并且很好地支持了这种情况和其他极端情况。从那以后,德米特里(Dmitry)改进了实施方式,鉴于他的往绩,我敢打赌他只会使它更安全。

特别是,您可以相信,如果当前有一个或多个被阻止的Wait呼叫,然后在呼叫Add之前以正增量进行呼叫Done,则不会取消阻止任何先前存在的Wait呼叫。

因此,您绝对可以这样做,例如:

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

不过,我理解您的问题,因为最近发布的文档中确实有一个令人困惑的陈述,但是让我们查看评论的历史以了解其实际解决的内容。

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。

如果阅读问题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()

这确实是坏的。只是应该对评论进行改进,使其更加具体,并避免您在阅读过程中所产生的似是而非但令人误解的理解。

2020-07-02