如何优雅地处理waitgroup中的压缩?
换句话说,在下面的代码片段中,如何捕获gorourines调用方法的恐慌/粉碎do()?
func do(){
str := "abc"
fmt.Print(str[3])
defer func() {
if err := recover(); err != nil {
fmt.Print(err)
}
}()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1; i++ {
wg.Add(1)
go do()
defer func() {
wg.Done()
if err := recover(); err != nil {
fmt.Print(err)
}
}()
}
wg.Wait()
fmt.Println("This line should be printed after all those invocations fail.")
}
Run Code Online (Sandbox Code Playgroud)
首先,注册一个要恢复的延迟函数应该是函数中的第一行,因为你最后这样做,它甚至不会被到达,因为之前的行/代码已经发生了defer恐慌,所以延迟函数没有被注册会恢复恐慌状态。
因此,将您的do()功能更改为:
func do() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Restored:", err)
}
}()
str := "abc"
fmt.Print(str[3])
}
Run Code Online (Sandbox Code Playgroud)
其次:仅此一项不会使您的代码正常工作,因为您调用了wg.Defer()一个延迟函数,该函数只会运行一次main()完成 - 这永远不会因为您wg.Wait()在main(). 因此wg.Wait()等待调用wg.Done(),但wg.Done()调用在返回之前不会运行wg.Wait()。这是一个僵局。
您应该在延迟函数中wg.Done()从函数中调用do(),如下所示:
var wg sync.WaitGroup
func do() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
wg.Done()
}()
str := "abc"
fmt.Print(str[3])
}
func main() {
for i := 0; i < 1; i++ {
wg.Add(1)
go do()
}
wg.Wait()
fmt.Println("This line should be printed after all those invocations fail.")
}
Run Code Online (Sandbox Code Playgroud)
输出(在Go Playground上尝试):
Restored: runtime error: index out of range
This line should be printed after all those invocations fail.
Run Code Online (Sandbox Code Playgroud)
这当然需要将wg变量移动到全局范围。do()另一种选择是将其作为参数传递。如果您决定采用这种方式,请注意,您必须传递一个指针WaitGroup,否则只会传递一个副本(WaitGroup是一种struct类型),并且调用WaitGroup.Done()副本不会对原始副本产生影响。
传递WaitGroup到do():
func do(wg *sync.WaitGroup) {
defer func() {
if err := recover(); err != nil {
fmt.Println("Restored:", err)
}
wg.Done()
}()
str := "abc"
fmt.Print(str[3])
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1; i++ {
wg.Add(1)
go do(&wg)
}
wg.Wait()
fmt.Println("This line should be printed after all those invocations fail.")
}
Run Code Online (Sandbox Code Playgroud)
输出是一样的。在Go Playground上尝试这个变体。