这里会涉及到一些defer的知识,有兴趣可以看我的另一篇关于defer的文章 https://www.jianshu.com/p/fec11caadaf6
数据结构
//runtime/runtime2.go
type _panic struct {
// 调用defer时入参的指针
argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
// panic 的参数
arg interface{} // argument to panic
// 指向更早的panic, 新的panic添加到链表头
link *_panic // link to earlier panic
// 当前panic是否被recover
recovered bool // whether this panic is over
// 当前panic是否被强制终止
aborted bool // the panic was aborted
}
这里会涉及到两个函数: panic和recover, 它们分别对应gopanic、gorecover
gopanic
//runtime/panic.go
func gopanic(e interface{}) {
gp := getg()
...
// 初始化panic
var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
atomic.Xadd(&runningPanicDefers, 1)
// 遍历 G 的defer链表
for {
d := gp._defer
if d == nil {
break
}
// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
// take defer off list. The earlier panic or Goexit will not continue running.
// defer已经被调用过, 跳过继续循环
if d.started {
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
d.fn = nil
gp._defer = d.link
freedefer(d)
continue
}
d.started = true // 标识被调用
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
p.argp = unsafe.Pointer(getargp(0))
//调用defer后面的函数。如果函数中包含了recover,那么会调用gorecover
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
p.argp = nil
// reflectcall did not panic. Remove d.
if gp._defer != d {
throw("bad defer entry in panic")
}
d._panic = nil
d.fn = nil
gp._defer = d.link
// trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
//GC()
pc := d.pc
sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
// 将 defer 放回缓存
freedefer(d)
// 已经有recover被调用
if p.recovered {
atomic.Xadd(&runningPanicDefers, -1)
gp._panic = p.link
// Aborted panics are marked but remain on the g.panic list.
// Remove them from the list.
// 移除被终止的panic
for gp._panic != nil && gp._panic.aborted {
gp._panic = gp._panic.link
}
if gp._panic == nil { // must be done with signal
gp.sig = 0
}
// Pass information about recovering frame to recovery.
gp.sigcode0 = uintptr(sp)
gp.sigcode1 = pc
// 恢复,调用gorecovery
mcall(recovery)
throw("recovery failed") // mcall should not return
}
}
preprintpanics(gp._panic)
// 终止程序
fatalpanic(gp._panic) // should not return
*(*int)(nil) = 0 // not reached
}
gorecover
//runtime/panic.go
func gorecover(argp uintptr) interface{} {
gp := getg()
p := gp._panic
if p != nil && !p.recovered && argp == uintptr(p.argp) {
// 将recovered设为true,说明已经defer后面的函数包含recover
p.recovered = true
return p.arg
}
return nil
}
recovery
//runtime/panic.go
func recovery(gp *g) {
...
// 这里会跳转到deferreturn
gogo(&gp.sched)
}
fatalpanic
//runtime/panic.go
func fatalpanic(msgs *_panic) {
...
systemstack(func() {
if startpanic_m() && msgs != nil {
atomic.Xadd(&runningPanicDefers, -1)
// 打印错误消息
printpanics(msgs)
}
docrash = dopanic_m(gp, pc, sp)
})
systemstack(func() {
exit(2) // 退出程序
})
*(*int)(nil) = 0 // not reached
}
总结:panic、recover和defer十分密切,所以在这里就一起总结了。编译阶段会将defer转换成deferproc,并在return前插入deferreturn。deferproc会从两级缓存中获取defer或者重新生成defer,defer会添加到G的defer链表头部。deferreturn时会遍历defer链表(后进先出)。当发生panic时, 会遍历G的defer链表,如发现defer后面的函数中包含recover,则会跳转到deferreturn,否则退出程序。
有疑问加站长微信联系(非本文作者)