Чтобы дождаться группы горутин, sync.WaitGroup
- идиоматическое решение.Вы можете добавить 1 к его счетчику при запуске новой программы (WaitGroup.Add()
), и эта программа может сигнализировать, что это сделано с WaitGroup.Done()
.Родительская программа может вызвать WaitGroup.Wait()
, чтобы дождаться завершения всех своих детей.
Вы можете делать то же самое на каждом уровне.Создайте WaitGroup
на каждом уровне, где запускаются дочерние программы, и возвращаются только тогда, когда возвращается Wait()
этой процедуры.
Вот как это применяется в вашем примере:
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
//level1
wg1 := &sync.WaitGroup{}
wg1.Add(1)
go func() {
defer wg1.Done()
fmt.Println("level1 started")
//level2
wg2 := &sync.WaitGroup{}
wg2.Add(1)
go func() {
defer wg2.Done()
fmt.Println("level2 started")
//level3
wg3 := &sync.WaitGroup{}
wg3.Add(1)
go func() {
defer wg3.Done()
fmt.Println("level3 started")
select {
case <-ctx.Done():
fmt.Println("Done called on level3")
case <-time.After(5 * time.Second):
fmt.Println("After called on level3")
}
fmt.Println("Level 3 ended.")
}()
select {
case <-ctx.Done():
fmt.Println("Done called on level2")
case <-time.After(7 * time.Second):
fmt.Println("After called on level2")
}
wg3.Wait()
fmt.Println("Level 2 ended.")
}()
select {
case <-ctx.Done():
fmt.Println("Done called on level1")
case <-time.After(10 * time.Second):
fmt.Println("After called on level1")
}
wg2.Wait()
fmt.Println("Level 1 ended.")
}()
time.Sleep(1 * time.Second)
cancel()
wg1.Wait()
fmt.Println("Main ended.")
Thisвыходы (попробуйте на Go Playground ):
level1 started
level2 started
level3 started
Done called on level1
Done called on level3
Level 3 ended.
Done called on level2
Level 2 ended.
Level 1 ended.
Parent ended.
Что важно из вывода:
Level 3 ended.
Level 2 ended.
Level 1 ended.
Main ended.
Уровни заканчиваются в порядке убывания уровня (снизувверх), закрывается с "Main ended."
.