我编写了 的第一个实现sync.WaitGroup
,并且很好地支持了这个和其他边缘情况。从那时起,Dmitry 改进了实施,鉴于他的记录,我敢打赌他只会让它更安全。
特别是,您可以相信,如果当前有一个或多个被阻止的Wait
呼叫,然后您在 callAdd
之前使用正 delta 进行呼叫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) {
来自 Russ 的日志评论指出:
同步:添加关于呼叫位置的注意事项 (*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()
这确实是坏掉了。应该改进评论以使其更具体,并避免您在阅读时产生的看似合理但具有误导性的理解。