go中如何使用recover
go 中如何使用 recover 恢复 panic。
常驻服务(比如 web server),程序 panic 之后,为了能够保持继续提供服务的能力,需要使用 recover 函数处理 panic 错误。使用 recover 函数时,有以下几点需要注意:
- 只在当前 goroutine 有效
go 源码中 panic 函数文档注释如下:
// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated with a non-zero exit code. This
// termination sequence is called panicking and can be controlled by the
// built-in function recover.
当函数 F 发生 panic 时,在 F 的 defer 函数执行后,会将控制权转交给 F 调用者 G。对 G 而言,函数 F 的行为会跟调用 panic 类似,终止 G 的正常执行流程。以此类推,整个程序最终会以非零退出码停止。对 G 而言,F 效果类似于调用 panic 函数,但并不完全等效,G 中无法用 recover 函数去管理 F 的 panic 退出。测试代码如下:
func panicTest() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover error:%+v", err)
panic("for panic")
}
}()
panic("panic mock")
}
func main() {
defer func() {
if err := recover(); err != nil {
log.Printf("main recover: %+v", err)
}
}()
go panicTest()
loop:
goto loop
}
程序测试结果:
❯ go run howtorecover.go
2022/11/13 16:56:08 recover error:panic mock
panic: panic mock [recovered]
panic: for panic
goroutine 18 [running]:
main.panicTest.func1()
/Users/brick/Desktop/howtorecover.go:13 +0x70
panic({0x1098440, 0x10c9b48})
/usr/local/Cellar/go/1.19.3/libexec/src/runtime/panic.go:884 +0x212
main.panicTest()
/Users/brick/Desktop/howtorecover.go:17 +0x49
created by main.main
/Users/brick/Desktop/howtorecover.go:47 +0x45
exit status 2
- 只在 defer 中有效
go 源码中 recover 注释如下:
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
goroutine 在 panic 后,控制权会转交当前函数的 defer 函数。所以 recover 如果不作用于 defer 中,无法获取 panic 后函数的控制权。在 go 源码注释中已有明确说明。
- recover 可嵌套调用
在 recover 中可继续调用 panic 和 recover。验证代码如下:
func recover2() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover2 error:%+v", err)
}
}()
panic("recover2")
}
func recover1() {
defer func() {
if err := recover(); err != nil {
log.Printf("recover1 error:%+v", err)
recover2()
}
}()
panic("recover1")
}
func main() {
defer func() {
if err := recover(); err != nil {
log.Printf("main recover: %+v", err)
}
}()
go recover1()
loop:
goto loop
}
代码输出如下:
❯ go run howtorecover.go
2022/11/13 17:06:52 recover1 error:recover1
2022/11/13 17:06:52 recover2 error:recover2