在Go异常处理(二)中我们介绍了gopanic
得代码实现,其中提到了mcall
用来回到正常得函数调用,也就是继续调用defer链中的一个defer,或者理解为当前gopanic函数已经执行完成了,然后模拟return退出gopanic
函数。
另外我们也提到了fatalpanic
函数,当mcall函数不能被调用时,在方法得最后会调用fatalpanic
。
代码精简如下:
// gopanic接收一个参数,这个参数就是通过panic函数传递的参数
func gopanic(e interface{}) {
// ...
// 当发生panic时,会立即处理当前g的_defer队列
for {
//调用mcall函数,相当于return
mcall(recovery)
}
// ...
// 致命错误,调用exit()退出
fatalpanic(gp._panic) // should not return
// ...
}
在gopanic
函数中,如果所有得defer中都没有使用recover
来处理panic,则在函数的最后会调用fatalpanic
函数来结束当前程序的运行。
为什么会结束程序的运行?
从设计角度来说:如果存在未处理的异常时,应该结束程序;从代码实现来说:是因为它调用exit函数。
runtime.fatalpanic(msgs *_panic)
func fatalpanic(msgs *_panic) {
// ...
/// 通过调用exit函数来结束程序
systemstack(func() {
// 无条件退出程序
exit(2)
})
}
所以为了防止程序崩溃,我们应该尽量在函数中使用recover函数来处理panic,但在正常的开发中我们也不可能在每个函数中定义defer,因为只要存在defer的定义,就会消耗一部分性能来处理defer链。
但时我们可以在一个功能开始的位置定义defer来处理panic防止程序崩溃:
func test(){
defer func() {
if err := recover(); err != nil {
log.Println(err)
}
}()
// 进行一些其它函数调用
testA()
testB()
testC()
panic("error")
}
这就要求我们明确当前函数的后续执行有没有可能放生panic。
我们应该将recover()反正距离panic最近的函数中,这样可以保证错误不会被传递的太远,这不是为了提高性能,而是为了让我们的业务在发现致命问题时最快的被修正。