09-异常处理 panic 和 recover
异常处理相关的两个内置函数 panic()
和 recover()
,这两个内置函数可以用来处理 Go 程序运行时发生的错误。panic()
函数用于主动抛出错误,recover()
函数用于捕获 panic()
抛出的错误。
宕机(panic)
Go 语言的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等,这些运行时错误会引起宕机。
一般而言,当宕机发生时,程序会中断运行,并立即执行在该 goroutine(线程)中被延迟的函数(defer机制),随后,程序崩溃并输出日志信息。日志信息包括 panic value 和函数调用的堆栈跟踪信息,panic value 通常是某种错误信息。
引发宕机有如下两种情况:
- 程序主动调用
panic()
函数,这样开发者可以及时发现错误,同时减少可能的损失。Go 语言程序在宕机时,会将堆栈和 goroutine 信息输出到控制台,所以,宕机也可以方便地确定发生错误的位置 - 程序产生运行时错误,由运行时检测并抛出。
发生 panic 后,程序会从调用 panic 的函数位置或发生 panic 的地方立即返回,逐层向上执行函数的 defer 语句,然后逐层打印函数调用堆栈,直到被 recover 捕获或运行到最外层函数而退出。
panic 的参数是一个空接口类型 interface{},所以,任意类型的变量都可以传递给 panic。panic()
函数的声明如下:
1 |
|
调用 panic 的方法非常简单,即 panic (xxx)
。例如:
1 |
|
panic 不但可以在函数正常流程中抛出,在 defer 逻辑中也可以再次调用 panic 或抛出 panic。defer 中的 panic 能够被后续执行的 defer 捕获。
当 panic()
触发的宕机发生时,panic()
后面的代码将不会被运行,但是在 panic()
函数前面已经运行过的 defer 语句依然会在宕机发生时发生作用,例如:
1 |
|
运行结果如下所示:
1 |
|
宕机前,defer 语句会被优先执行,由于第 5 行的 defer 后执行,因此在宕机前,这个 defer 会优先处理,随后才是第 4 行的 defer 对应的语句,这个特性可以用来在宕机发生前进行宕机信息处理。
宕机恢复(recover)
无论代码运行错误是由 Runtime 层抛出的 panic 崩溃,还是主动触发的 panic 崩溃,都可以配合 defer 和 recover 实现错误的捕捉和恢复,让代码发生崩溃后允许继续运行。
recover()
函数用来捕获 panic,阻止 panic 继续向上传递。recover()
函数可以和 defer 语句一起使用,但 recover()
函数只有在 defer 后面的函数体内被直接调用才能捕获 panic 终止异常,否则会返回 nil,异常继续向外传递。
可以有连续多个 panic 被抛出,连续多个被抛出的场景只能出现在延迟调用中。虽然有多个 panic 被抛出,但是只有最后一次的 panic 才能被捕获,例如:
1 |
|
运行结果为:
1 |
|
包中 init()
函数引发的 panic 只能在 init()
函数中捕获,在 main()
函数中无法被捕获,这是因为 init()
函数优先于 main()
函数执行。
函数并不能捕获内部新启动的 goroutine 所抛出的 panic,例如:
1 |
|
panic 和 recover 的关系如下:
- 有 panic 没 recover,程序宕机;
- 有 panic 也有 recover,程序不会宕机,执行完对应的 defer 语句后,从宕机点退出当前函数后继续执行。