等待不确定数量的goroutines

Bry*_*yce 6 go

我有一个代码,其中一个goroutine会触发不确定数量的儿童goroutine,这反过来会触发更多goroutines等等.我的目标是等待所有的儿童goroutine完成.

我不知道我将提前解雇的goroutines的总数,所以我不能使用sync.WaitGroup,理想情况下我不必人为地限制通过channel-as-semaphore模式运行的goroutines总数.

简单地说,我想在每个goroutine中都有一个本地通道或等待组作为等待其所有子节点的信号量,但这会导致每个goroutine都在消耗堆栈空间,而它的所有后代都完成了.

现在我的想法是在goroutine被触发时增加原子计数器(在父节点中,以避免在父节点完成后子节点开始运行时虚假地击中零),在goroutine结束时减少它,并定期检查它是否相等为零.

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

Gus*_*yer 11

我编写了第一个实现sync.WaitGroup,并且这个和其他边缘情况得到了很好的支持.从那以后,德米特里改进了实施,并且根据他的记录,我打赌他只是让它更安全.

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

所以你绝对可以做到这一点,例如:

var wg sync.WaitGroup
wg.Add(1)
go func() {
    wg.Add(1)
    go func() {
        wg.Done()
    }()
    wg.Done()
}()
wg.Wait()
Run Code Online (Sandbox Code Playgroud)

自从代码首次集成以来,我实际上在生产中使用了等效的逻辑.

作为参考,这个内部评论在第一个实现中已经存在,并且仍然存在:

// 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.
Run Code Online (Sandbox Code Playgroud)

这描述了在触及实现时要记住的不同竞争条件,但请注意,即使在竞赛时也会使用G1Add(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) {
Run Code Online (Sandbox Code Playgroud)

Russ的日志评论说:

sync:添加关于调用位置的警告(*WaitGroup).Add

修正问题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()
Run Code Online (Sandbox Code Playgroud)

这确实被打破了.评论应该进一步改进,以便更具体,避免在阅读时你理解但误导的理解.