panic和recover
panic
panic的数据结构
type _panic struct{
argp unsafe.Pointer //defer的参数空间地址
arg interface //panic的参数
link *_panic //链接之前的panic
recovered bool//是否被恢复
aborted bool //是否被终止
}
同defer一样,每个goroutine数据结构中实际上也有一个panic链表指针,该指针指向一个panic的单链表,每次执行一个panic时就将panic插入到单链表表头
panic单链表由一个个_panic结构体组成
案例
func A1(){
...
panic("panicA1")
...
func A(){
defer A1()
defer A2()
panic("panicA")
.....
}
A中执行到panic时,defer链表中已经注册了A1和A2,panic后面的代码将不被执行
panic执行defer时会先把defer结构体中的start置为true,标记它已经开始执行,并且把_panic字段指向当前的panic,表示这个defer由这个panic触发,如果函数A2能正常结束,A2就会被移除,继续执行下一个defer
假设A2正常结束,执行defer A1()前,在defer链表A1中的start和_panic字段进行标记,当执行到A1函数中的panicA1时,panicA1成为当前执行的panic如图:
panicA1成为当前执行的panic时,defer链表A1已经执行,并且触发它·执行的并不是panicA1,而是panicA
执行完后,panicA被终止,defer链表A1被移除
注意:打印异常信息时,是从panic链表尾开始打印的,所以先打印panicA,再打印panicA1
没有recover的发生时panic的处理逻辑就是这样,这里关键点有两个第一个是panic执行defer函数的方式,先标记后释放,目的就是终止之前发生的panic,如案例中的panicA。第二个是异常信息的处理方式,先panic的先打印。
recover
recover就是把当前执行panic置为恢复,也就是把panic结构中的recover字段置为true
案例
func A(){
defer A1()
defer A2()
panic("panicA")
}
func A2(){
p := recover()
fmt.Println(p)
panic("panicA1")
}
执行到panicA2时,它成为当前执行的panic,然后执行defer链表,但defer链表中的A2已经执行,所以把panicA给终止,并把A2从defer链表中移除
去除defer链表中的A2后,因为当前执行的panic为panicA2,所以A1中的_panic被标记为panicA2
A1结束后被移除,defer链表为空,接下来就要输出异常信息了
注意:对于已经recover的panic,打印时会加上recover标记