1 概述
在go中,经常看到 panic 和 recover 与 defer配套使用。我们具体来看。
2 panic
panic的英文是恐慌。下面是panic方法的官方介绍,它会中止当前协程的正常执行。当一个方法F调用panic时,F的正常执行立即停止。任何被 F defer的方法,将会以正常的方式执行,然后F返回给它的调用方。
// 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.
func panic(v any)
import (
"fmt"
)
func main() {
A()
return
}
func A() {
fmt.Println("A before panic")
panic("A panic")
fmt.Println("A after panic")
return
}
执行结果:
A before panic
panic: A panic
goroutine 1 [running]:
main.A()
/Users/yiyunjie/go/src/Test/main.go:14 +0x68
main.main()
/Users/yiyunjie/go/src/Test/main.go:8 +0x1c
Process finished with the exit code 2
我们可以看到,方法A正常地执行了第一个打印以及panic,之后协程中止运行, 后续的代码未按正常流程执行,协程以非正常方式退出,退出码为2。
很显然,在保持服务可用性的角度来看,panic并不是一种友好的退出方式,有没有一种更温和的处理方式呢?答案就是 defer 和 recover。
3 recover
我们在上一篇文章中讲过,defer会在函数return之前执行,但是panic很显然并不是return,那怎么办呢?这里就用到 recover。
recover 可以中止panic造成的协程非正常退出,从它的名称 恢复 就能看出一二,相当于给panic造了一个兜底的安全网。我们来看看。
// 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.
func recover() any
示例:
package main
import (
"fmt"
)
func main() {
A()
return
}
func A() {
r := recover()
fmt.Println(fmt.Sprintf("recover r:%+v", r))
fmt.Println("A before panic")
panic("A panic")
fmt.Println("A after panic")
return
}
输出
recover r:<nil>
A before panic
panic: A panic
goroutine 1 [running]:
main.A()
/Users/yiyunjie/go/src/Test/main.go:17 +0xdc
main.main()
/Users/yiyunjie/go/src/Test/main.go:8 +0x1c
Process finished with the exit code 2
很明显,如果直接把recover定义在函数体里面,它并不会中止panic,而是像正常代码一样顺序执行。所以我们必须要用到 defer,再试一次。
package main
import (
"fmt"
)
func main() {
A()
return
}
func A() {
defer func() {
r := recover()
fmt.Println(fmt.Sprintf("recover r: %+v, type: %T", r, r))
}()
fmt.Println("A before panic")
panic("A panic")
fmt.Println("A after panic")
return
}
输出
A before panic
recover r: A panic, type: string
Process finished with the exit code 0
这次协程正常地退出了,recover 输出的内容,正好就是 panic 输入的内容。
4 改造
我们获取到了panic的内容,想要对它做一些处理,再return给调用方,应该怎么做呢?
(待续)