这个问题有两个部分。
首先,我们需要以某种方式停止子Goroutine,即使父Goroutine停止,所有子Goroutine也应该得到通知并停止 - 这是一个向下而不是向上的停止信号层次结构。
另一方面,父Goroutine需要等待其子Goroutine完成。否则,我们可能会在某些Goroutine正确完成之前从Goroutine返回甚至退出应用程序。
为了简单起见,我们忽略实现错误处理、超时等内容。
对于处理第一个问题,我们使用
context.Context
,它为我们提供了一个很好的执行上下文处理工具层次结构;对于解决第二个问题,我们使用
sync.WaitGroup
,它允许我们等待一组Goroutine完成其任务。一个简单的演示如下:
func main() {
all := &sync.WaitGroup{}
rootCtx, rootCancel := context.WithCancel(context.Background())
all.Add(1)
go level1(rootCtx, all)
go func() {
time.Sleep(time.Second * 3)
rootCancel()
}()
all.Wait()
}
func level1(parent context.Context, all *sync.WaitGroup) {
defer all.Done()
l1Ctx, l1Cancel := context.WithCancel(parent)
defer l1Cancel()
for i := 0; i < 3; i++ {
all.Add(1)
go level2(l1Ctx, all)
}
for {
select {
case <-parent.Done():
return
case <-time.After(time.Second):
log.Println(`level1`)
}
}
}
func level2(parent context.Context, all *sync.WaitGroup) {
defer all.Done()
for {
select {
case <-parent.Done():
return
case <-time.After(time.Second):
log.Println(`level2`)
}
}
}
这给我们一些输出,例如:
[ info ] level2
[ info ] level2
[ info ] level2
[ info ] level1
[ info ] level2
[ info ] level1
[ info ] level2
[ info ] level2
目前没有官方的包提供将context.Context
和sync.WaitGroup
结合起来的功能。最接近的是errgroup
,可以通过一些技巧实现类似的功能。