底层逻辑
在每个goroutine也有一个指针指向_panic链表表头,然后每增加一个panic就会在链表头部加入一个_panic结构体。当所有的defer执行完后,_panic链表就会从尾部开始打印panic信息了,也就是说先发生的panic先打印信息。
_panic结构体
在go源码的runtime/runtime2.go中有_panic的结构体信息
type _panic struct {
argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
arg interface{} // argument to panic
link *_panic // link to earlier panic
pc uintptr // where to return to in runtime if this panic is bypassed
sp unsafe.Pointer // where to return to in runtime if this panic is bypassed
recovered bool // whether this panic is over
aborted bool // the panic was aborted
goexit bool
}
结构体中的 pc、sp 和 goexit 三个字段都是为了修复 runtime.Goexit 带来的问题引入的。runtime.Goexit 能够只结束调用该函数的 Goroutine 而不影响其他的 Goroutine,但是该函数会被 defer 中的 panic 和 recover 取消,引入这三个字段就是为了保证该函数的一定会生效。
嵌套panic
接下来用个嵌套的panic例子来强化理解一下上面讲的
package main
func main() {
defer func() {
defer func() {
panic("双重嵌套:panic")
}()
panic("panic1")
}()
defer func() {
panic("panic2")
}()
panic("main-panic")
}
输出:
panic: main-panic
panic: panic2
panic: panic1
panic: 双重嵌套:panic
goroutine 1 [running]:
main.main.func1.1()
/home/zheng/STUDY/GoWork/demo/main.go:6 +0x39
panic(0x466460, 0x48a2b8)
/usr/local/go/src/runtime/panic.go:965 +0x1b9
main.main.func1()
/home/zheng/STUDY/GoWork/demo/main.go:8 +0x5b
panic(0x466460, 0x48a2c8)
/usr/local/go/src/runtime/panic.go:965 +0x1b9
main.main.func2()
/home/zheng/STUDY/GoWork/demo/main.go:11 +0x39
panic(0x466460, 0x48a298)
/usr/local/go/src/runtime/panic.go:965 +0x1b9
main.main()
/home/zheng/STUDY/GoWork/demo/main.go:13 +0x68
图解:
recovery
这个函数的目的很简单,就是把_panic结构体中的recovered字段设为true,不过它在执行修改前会判断当前有没有panic和当前panic有没有被恢复过。可以通过源码runtime.gorecover中体现出来
func gorecover(argp uintptr) interface{} {
gp := getg()
p := gp._panic
if p != nil && !p.recovered && argp == uintptr(p.argp) {
p.recovered = true
return p.arg
}
return nil
}
所以recovery要通过defer在panic之后调用才会生效