关于panic,接下来主要讲panic的执行机制和顺序,我会通过多个例子来讲解不同情况下panic逻辑的处理流程。
首先这是panic的结构体
首先,通过上一节defer的讲解我们知道,多个defer组成列表挂在当前协程goroutine的成员变量_defer字段下的。
同理在goroutine结构体上还有个字段是用来挂接多个_panic结构体的。
func deferA(){ } //伪代码 func main(){ defer deferA() panic("MAIN") }
type _defer struct { siz int32 started bool //1、panic执行defer,会先将defer的started置为true sp uintptr pc uintptr fn *funcval _panic *panic// 2、panic字段指向当前执行的panic link *_defer }
且panic触发了defer方法,所以对应
_deferA.started=true,
_deferA._panic=panicMAIN
如图
承接例子一,如果增加一个defer
func deferA(){ } func deferB(){ } //伪代码 func main(){ defer deferA() defer deferB() panic("MAIN") }
那么按照defer的注册顺序,那么deferB最先执行如图
当deferB执行完毕后,移除deferB的节点,继续执行deferA。直到最后deferA方法执行完被移除后,输出协程的panicMAIN信息
基于例子二,我在defer中出现panic
func deferA(){ panic("A") } func deferB(){ } //伪代码 func main(){ defer deferA() defer deferB() panic("MAIN") }
recover的作用很简单,就是把当前执行的panic置为已恢复,也就是把panic的recovered字段置为true;
看段代码
func deferA(){ panic("A") } func deferB(){ recover() } //伪代码 func main(){ defer deferA() defer deferB() panic("MAIN") }
首先,因为deferB中的recover使得panicMAIN被恢复并被移除,接下来,协程仍然要执行接下来的defer链表的节点deferA,而不是说恢复协程的正常处理逻辑;
什么?
没看懂问题?
就是既然当前唯一的panic ———— panicMAIN被恢复了,
按理目前暂时没有panic来触发defer流程了,
是什么机制使其能继续执行defer流程,继续执行deferA呢?
首先在deferB被移除前 (前提是deferB中的_panic字段指向的panicMAIN.recovered=true,说明触发deferB的panic已经被恢复) ,会记录保存deferB的sp和pc,并移除deferB节点;
sp和pc是注册defer函数时保存的
_defer.sp //main函数的栈指针
_defer.pc //声明deferB的时候调用deferproc函数的返回地址。
对于main函数
func deferA(){ panic("A") } func deferB(){ recover() } //伪代码 func main(){ defer deferA() defer deferB() panic("MAIN") }
其对应的伪指令
func main(){ r = runtime.deferproc(0,A) if r>0{ goto ret } r = runtime.deferproc(0,B) if r>0{ goto ret } // users code ret: runtime.deferreturn() }
接下来通过上述这两个字段来继续跳入defer执行链执行。
问题是:通过这两个字端,是如何继续跳入defer执行链呢。
首先,移除掉deferB后:
首先 这个deferproc的返回值是被编译器保存在寄存器中的,所以只要将r置为1,就可以执行goto ret,跳转到deferreturn这里,继续执行defer链表。
通过判断比较defer链表中每个defer项的的栈指针来判断当前执行的defer是否属于main函数,从而继续执行当前main函数的defer流程。(defer文章有讲,可以先去了解下defer的资料)
注意!!!,到此时才会触发每次defer移除时候的检查流程,发现panicMAIN的recovered=true,所以移除panicMAIN
然后继续执行deferA,此时deferA中有panicA,则如图:
然后就是常规的panic和defer处理了。
我们可以看到,panic中的recovered和aborted会影响panic的输出;
所以,问题来了,当有个panic recovered既为true、且aborted也为true的时候,此时的panic该如何处理?
那么也就来到最后的一个例子:
首先,我们先给出能触发上述情况的代码:
func deferA(){ } func deferB(){ recover() panic("B") } //伪代码 func main(){ defer deferA() defer deferB() panic("MAIN") }