创建多个goroutine,它们将在以多级方式进行处理时嵌套goroutine(想象一棵goroutine树,每个级别可以有很多叶子)。
按顺序正常关闭这些goroutine 并等待它们回来的惯用方式是什么?顺序是最下面的(最深的孩子在最前面),并且还假设我不知道我会事先(动态)启动多少个goroutine。
下面的示例仅以无序方式正常关闭了它们。
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
//level1
go func() {
fmt.Println("level1 started")
//level2
go func() {
fmt.Println("level2 started")
//level3
go func() {
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")
}
}()
select {
case <-ctx.Done():
fmt.Println("Done called on level2")
case <-time.After(7* time.Second):
fmt.Println("After called on level2")
}
}()
select {
case <-ctx.Done():
fmt.Println("Done called on level1")
case <-time.After(10* time.Second):
fmt.Println("After called on level1")
}
}()
time.Sleep(1*time.Second)
cancel()
time.Sleep(1 * time.Second)
}
答案 0 :(得分:2)
要等待一组goroutine,sync.WaitGroup
是惯用的解决方案。您可以在启动新的goroutine(WaitGroup.Add()
)时为其计数器加1,并且goroutine可以发出信号,表明已使用WaitGroup.Done()
完成。父goroutine可以调用WaitGroup.Wait()
以等待其所有子项完成。
您可以在每个级别上执行相同的操作。在启动子级goroutine的每个级别上创建一个WaitGroup
,并且仅在该goroutine的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.")
这将输出(在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."
结尾。
答案 1 :(得分:0)
一种可能的方法,我也说惯用的,方法是通过strict{}
通道。每当您希望终止所述goroutine时,只需向此通道shutdown <- struct{}{}
中写入一个空结构即可。这应该做的工作。
或者,您可以关闭通道,通过将false
作为<-
的第二个返回值来识别它,但是我建议仅在需要与多个goroutine共享此通道时才使用它。总的来说,我发现这种方法有点伪劣和容易出错。
附带说明:在您的示例中,关闭goroutine的方式是,一旦上下文被取消,所有goroutine将返回。不知道在一般情况下这是否有很多好处。也许就您而言。