您必须将一些代码“注入”到作为新 goroutine 启动的函数中:您必须调用延迟函数,在该函数中调用 recover()。这是从恐慌状态中恢复的唯一方法。见相关:Why does `defer recover()` not catch panics?
例如:
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}()
panic("catch me")
}()
这将输出(在Go Playground 上尝试):
Caught: catch me
在您启动的每个 goroutine 中都执行此操作是不可行的,但当然您可以将恢复日志记录功能移至命名函数,然后调用它(但当然要延迟):
func main() {
go func() {
defer logger()
panic("catch me")
}()
time.Sleep(time.Second)
}
func logger() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}
这将输出相同的结果(在Go Playground 上尝试)。
另一个更方便、更紧凑的解决方案是创建一个实用函数,一个接收函数并负责恢复的“包装器”。
这就是它的样子:
func wrap(f func()) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}()
f()
}
现在使用起来更简单了:
go wrap(func() {
panic("catch me")
})
go wrap(func() {
panic("catch me too")
})
它会输出(在Go Playground上试试):
Caught: catch me
Caught: catch me too
最后说明:
请注意,启动实际的 goroutine 发生在 wrap() 之外。这使调用者可以选择是否需要新的 goroutine,只需在 wrap() 调用前加上 go。通常这种方法在 Go 中是首选的。这允许您通过将任意函数传递给wrap() 来执行任意函数,并且即使您不希望在新的 goroutine 中同时运行它,它也会“保护”其执行(通过从恐慌中恢复、正确记录/报告它)。