我有一个代码,其中一个goroutine将触发不确定数量的儿童goroutine,这反过来将触发更多goroutines等。我的目标是等待所有的儿童goroutines完成。
我不知道我将提前解雇的goroutines的总数,所以我不能使用sync.WaitGroup,理想情况下我不必人为地限制通过{运行的goroutines总数{3}}模式。
简单地说,我想在每个goroutine中都有一个本地频道或等待组作为等待其所有子节点的信号量,但这会导致每个goroutine在所有后代完成时都会消耗堆栈空间。
现在我的想法是在goroutine被触发时增加channel-as-semaphore(在父节点中,以避免在父节点完成后子节点开始运行时虚假地击中零),在goroutine结束时减少它,并定期检查它是否等于零。
我基本上是在正确的轨道上,还是有更优雅的解决方案?
答案 0 :(得分:10)
我编写了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()
自从代码首次集成以来,我实际上在生产中使用了等效的逻辑。
作为参考,这个内部评论在第一个实现中已经存在,并且仍然存在:
// 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()
这确实被打破了。评论应该进一步改进,以便更加具体,避免在阅读时你理解但误导的理解。
答案 1 :(得分:0)
当然你可以使用sync.WaitGroup
来完成你的任务,它实际上是一个完美的契合,专为此而设计。您要创建的goroutine的数量是不不确定。它只是在运行时才知道的值,然后它就是完全。每个go
语句都会创建一个新的goroutine。 之前这样的go
语句,无论执行多少次,都必须执行
wg.Add(1)
和 insdide 每个goroutine put
defer wg.Done()
作为第一个声明。现在你可以做到
wg.Wait
等待所有你的goroutines完成。
答案 2 :(得分:0)
我喜欢WaitGroup
的简单。关于WaitGroup
,我不喜欢的一件事就是必须在你的goroutine中传递对它的引用,因为你会将并发逻辑与业务逻辑混合在一起。此外,在您的情况下,如果您不小心,它可能会变得更加复杂和容易出错。
所以我想出了这个通用函数来解决这个问题:
// Parallelize parallelizes the function calls
func Parallelize(functions ...func()) {
var waitGroup sync.WaitGroup
waitGroup.Add(len(functions))
defer waitGroup.Wait()
for _, function := range functions {
go func(copy func()) {
defer waitGroup.Done()
copy()
}(function)
}
}
以下是我将如何使用它来解决您的问题:
func1 := func() {
for char := 'a'; char < 'a' + 3; char++ {
fmt.Printf("%c ", char)
}
}
func2 := func() {
for number := 1; number < 4; number++ {
fmt.Printf("%d ", number)
}
}
func3 := func() {
Parallelize(func1, func2)
}
Parallelize(func3, func3) // a a 1 1 b b 2 2 c c 3 3
如果您想使用它,可以在https://github.com/shomali11/util
找到它