This problem has two parts.
First we need to stop child goroutines somehow in a way that even if a parent goroutines stops, all it's children should get notified and stop - a hierarchy of stop signals that goes down but not up.
On the other hand the parent needs to wait for it's children until they are done. Otherwise we would return from a goroutine or even exit from the app before some goroutines are finished properly.
For simplicity we ignore implementing error handling, timeouts and the like.
For handling the first problem we use context.Context
which gives us a nice hierarchy of execution context handling tools and for solving the second problem we use sync.WaitGroup
which allows us to wait for a group of goroutines to complete their tasks. A simple demonstration would be:
func main() {
all := &sync.WaitGroup{}
rootCtx, rootCancel := context.WithCancel(context.Background())
all.Add(1)
go level1(rootCtx, all)
// just to simulate stop, we could use an os signal instead
// app ends after 3 seconds
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
// other cases if any,
// this is a sample
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`)
}
}
}
Which gives us some output like:
[ info ] level2
[ info ] level2
[ info ] level2
[ info ] level1
[ info ] level2
[ info ] level1
[ info ] level2
[ info ] level2
Currently there is no official package that provide a functionality which combines context.Context
and sync.WaitGroup
. The nearest thing is an errgroup
which can resemble this functionality with some hacks.