Go异常处理的的用法
go中异常处理用法是defer...recover...panic
组合
panic
用于抛出异常,recover
用于拦截未处理的panic
,panic
会导致函数退出,函数推出前会执行defer
定义的函数,从而导致panic
被recover
函数拦截
package main
import "log"
func main() {
defer func() {
if err := recover(); err != nil {
log.Println(err)
}
}()
test()
}
func test(){
panic("error")
}
panic的实现原理
panic
被编译后对应的是runtime.gopanic
,代码如下:
// gopanic接收一个参数,这个参数就是通过panic函数传递的参数
func gopanic(e interface{}) {
gp := getg()
// 获取g并对g做一些可预知的错误判断
// ...
///下面创建一个_panic
var p _panic
p.arg = e
p.link = gp._panic
// 将panic放在g中的_panic链表头部
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
// 当发生panic时,会立即处理当前g的_defer队列
for {
d := gp._defer
// 如果不存在defer则跳出
if d == nil {
break
}
//如果存在defer则判断defer是否开始执行
if d.started {
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
if !d.openDefer {
// For open-coded defers, we need to process the
// defer again, in case there are any other defers
// to call in the frame (not including the defer
// call that caused the panic).
d.fn = nil
gp._defer = d.link
freedefer(d)
continue
}
}
// 标记为开始状态
d.started = true
// 将g中的panic复制给当前defer,从以上代码可以看出,panic会为每一个defer设置当前panic
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
done := true
// 无论怎样都会调用defer定义的函数
if d.openDefer {
done = runOpenDeferFrame(gp, d)
if done && !d._panic.recovered {
addOneOpenDeferFrame(gp, 0, nil)
}
} else {
p.argp = unsafe.Pointer(getargp(0))
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
}
// 调用完成后,以下代码清空当前defer的引用
p.argp = nil
// reflectcall did not panic. Remove d.
if gp._defer != d {
throw("bad defer entry in panic")
}
d._panic = nil
// 将gp中的_deger指向_defer链表上的下一个deger对象
if done {
d.fn = nil
gp._defer = d.link
freedefer(d)
}
// 判断panic是否被处理,如果被处理则销毁panic
if p.recovered {
// 退出gopanic函数并回到正常的函数调用
mcall(recovery)
// 如果mcall会返回到这里,说明存在错误,则调用throw抛出致命错误导致调用exit()函数
throw("recovery failed") // mcall should not return
}
}
// 打印错误堆栈信息
preprintpanics(gp._panic)
// 致命错误,调用exit()退出
fatalpanic(gp._panic) // should not return
*(*int)(nil) = 0 // not reached
}
- 编译器将panic编译为runtime.gopanic函数
- 在gopanic函数中,将调用defer链中对应的函数
- 如果存在recover来处理当前panic则嗲用mcall函数回到正常的函数调用中
- 如果所有的defer链被执行完成后还是没有处理panic则调fatalpanic函数
- fatalpanic会导致程序退出,因为它内部调用了exit
从上面代码的分析,至少可以得出以下结论
- panic会触发defer链被调用
- defer链始终会被完全调用