panic defer 顺序

协程遇到panic时,遍历本协程的defer链表,并执行defer。在执行defer过程中,遇到recover则停止panic,返回recover处继续往下执行。如果没有遇到recover,遍历完本协程的defer链表后,向stderr抛出panic信息。从执行顺序上来看,实际上是按照先进后出的顺序执行defer。

说有如下程序(main.go),写出运行之后的结果:



package main



import “fmt”



func main(){
defer_call()
fmt.Println(“333 Helloworld”)
}



func defer_call() {
defer func(){
fmt.Println(“11111”)
}()
defer func(){
fmt.Println(“22222”)
}()



defer func() {
if r := recover(); r!= nil {
fmt.Println("Recover from r : ",r)
}
}()

defer func(){
fmt.Println("33333")
}()

fmt.Println("111 Helloworld")

panic("Panic 1!")

panic("Panic 2!")

fmt.Println("222 Helloworld") } 我直接贴出运行结果:


111 Helloworld
33333
Recover from r : Panic 1!
22222
11111
333 Helloworld
如果你做对了,建议跳过。其实我也只是把自己的验证过程记录如下,以便以后查阅。



我们用上一篇文章所搭建的golang的gdb调试环境来具体分析下为什么会是这个结果。



编译源代码使用以下命令, 这里的-l参数的意思和上面一样, 如果有需要还可以加-N参数:



/home/james/workspace/go_src/bin/go build -gcflags “-l” main.go
1
对这个编译方法有疑问的可以参考上一篇文章。
编译后使用gdb运行:



go里面的函数符号名称的命名规则是包名称.函数名称, 例如主函数的符号名称是main.main, 运行时中的newobject的符号名称是runtime.newobject.
首先给主函数下一个断点,给我们第一个panic(“Panic 1!”)所在行下一个断点,然后运行:



单步运行之后,我们可以找到panic函数所对应的源码:



在上一篇文章中所准备的源码中找到对应的文件src/rumtime/panic.go:425,即panic函数具体实现如下:



// The implementation of the predeclared function panic.
func gopanic(e interface{}) {
gp := getg() // getg()返回当前协程的 g 结构体指针,g 结构体描述 goroutine
if gp.m.curg != gp {
print(“panic: “)
printany(e)
print(“\n”)
throw(“panic on system stack”)
}



// m.softfloat is set during software floating point.
// It increments m.locks to avoid preemption.
// We moved the memory loads out, so there shouldn't be
// any reason for it to panic anymore.
if gp.m.softfloat != 0 {
gp.m.locks--
gp.m.softfloat = 0
throw("panic during softfloat")
}
if gp.m.mallocing != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic during malloc")
}
if gp.m.preemptoff != "" {
print("panic: ")
printany(e)
print("\n")
print("preempt off reason: ")
print(gp.m.preemptoff)
print("\n")
throw("panic during preemptoff")
}
if gp.m.locks != 0 {
print("panic: ")
printany(e)
print("\n")
throw("panic holding locks")
}

var p _panic
p.arg = e
p.link = gp._panic
gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

atomic.Xadd(&runningPanicDefers, 1)

for {
d := gp._defer // 获取当前协程defer链表的头节点
if d == nil {
break // 当前协程的defer都被执行后,defer链表为空,此时退出for循环
}

// 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.
if d.started { // 发生panic后,在defer中又遇到panic(),则会进入这个代码块
if d._panic != nil {
d._panic.aborted = true
}
d._panic = nil
d.fn = nil
gp._defer = d.link
freedefer(d) // defer 已经被执行过,则释放这个defer,继续for循环。
continue
}

// Mark defer as started, but keep on list, so that traceback
// can find and update the defer's argument frame if stack growth
// or a garbage collection happens before reflectcall starts executing d.fn.
d.started = true

// Record the panic that is running the defer.
// If there is a new panic during the deferred call, that panic
// will find d in the list and will mark d._panic (this panic) aborted.
d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

p.argp = unsafe.Pointer(getargp(0))
reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) // 执行当前协程defer链表头的defer
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 // 从defer链中移除刚刚执行过的defer

// 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
freedefer(d) // 释放刚刚执行过的defer
if p.recovered { // defer()中遇到recover后进入这个代码块
atomic.Xadd(&runningPanicDefers, -1)

gp._panic = p.link
// Aborted panics are marked but remain on the g.panic list.
// Remove them from the list.
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
mcall(recovery) // 跳转到recover()处,继续往下执行
throw("recovery failed") // mcall should not return
}
}

// ran out of deferred calls - old-school panic now
// Because it is unsafe to call arbitrary user code after freezing
// the world, we call preprintpanics to invoke all necessary Error
// and String methods to prepare the panic strings before startpanic.
preprintpanics(gp._panic)
startpanic()

// startpanic set panicking, which will block main from exiting,
// so now OK to decrement runningPanicDefers.
atomic.Xadd(&runningPanicDefers, -1)

printpanics(gp._panic) // 输出panic信息
dopanic(0) // should not return
*(*int)(nil) = 0 // not reached }

Category golang