关于panic,接下来主要讲panic的执行机制和顺序,我会通过多个例子来讲解不同情况下panic逻辑的处理流程。
首先这是panic的结构体
首先,通过上一节defer的讲解我们知道,多个defer组成列表挂在当前协程goroutine的成员变量_defer字段下的。
同理在goroutine结构体上还有个字段是用来挂接多个_panic结构体的。
func deferA(){
}
//伪代码
func main(){
defer deferA()
panic("MAIN")
}
先向当前协程的_defer字段上挂接defer遇到panic,此时向协程的panic字段挂接panic因为遇到了panic,所以程序停止,从而触发panic,而panic也会触发defer方法。
首先我们可以看下_defer结构体:
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执行完后,所有在panic链表上的项都会被输出,顺序于panic发生的顺序一致。 例子二
承接例子一,如果增加一个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")
}
当触发panicMAIN的时候,如图可知当前执行的是deferB,deferB执行完后如图:
此时开始执行deferA,但是此时deferA中触发了panicA,如图:
从上图可以看到,panicA加入到了_panic链表表头,那么按理当前最先执行的应该是panicA。(panic链表也是秉承LIFO原则)
然而去到deferA中发现当前触发deferA的是panicMAIN,也就是当前执行的是panicMAIN,显然不符合我们panic链表的LIFO原则,那么系统会强制将panicMAIN进行终止,即将panicMAIN的panic结构体的aborted字段置为true,标识当前的panic已经终止。
然后将panicA置为执行状态,触发deferA,如图:
deferA执行完,协程先后输出当前_panic链表信息:
1、panicMAIN(aborted)信息;(panicMAIN是已终止状态,所以会附带一个aborted信息)
2、panicA的信息; 例子四 增加recover
recover的作用很简单,就是把当前执行的panic置为已恢复,也就是把panic的recovered字段置为true;
看段代码
func deferA(){
panic("A")
}
func deferB(){
recover()
}
//伪代码
func main(){
defer deferA()
defer deferB()
panic("MAIN")
}
当触发panicMAIN的时候,如图会先执行deferB,且在deferB中有recover,那么recover便会将panicMAIN的recovered置为true,
当deferB执行完后,panic处理流程会进行一次检查,对那些已恢复的panic进行移除。
所以当发现panicMAIN的recovered=true,那么便会移除掉panicMAIN。
小贴士:
首先,因为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后:
通过保存的deferB.SP 找到main函数的栈指针;通过pc可以恢复到 声明deferB的时候调用deferproc函数的返回地址也就是如图所示的位置
接下来会执行r的判断,如果r<=0,那么便会跳过判断执行users code的逻辑导致main函数被重复执行。
首先 这个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 ,则会移除panicpanic.aborted = true 则会将当前panic标记为停止,且在协程输出的时候,将信息附带打印出来;但是aborted=true,并不会移除掉panic节点。所以,问题来了,当有个panic recovered既为true、且aborted也为true的时候,此时的panic该如何处理?
那么也就来到最后的一个例子:
例子五,recovered=true & aborted = true首先,我们先给出能触发上述情况的代码:
func deferA(){
}
func deferB(){
recover()
panic("B")
}
//伪代码
func main(){
defer deferA()
defer deferB()
panic("MAIN")
}
panicMAIN触发执行deferB,且deferB中有recover,那么,panicMAIN.recovered = true
recover执行完后,紧接着出发panicB,如图:
由图中可以看到,当前执行的panic改为panicB了,但是目前执行的deferB仍然是受panicMAIN触发,此时便会触发aborted(可以参考例子五的aborted处理)
那么此时panicMAIN.aborted = true,如图:
此时 deferB之行结束,此时deferA就是由panicB触发执行的了:
,再然后deferA之行结束,那么将会打印panic链表信息,
此时panicMAIN虽然被恢复了,但是也仍然会被打印并附带 recovered和aborted
由此可知,panic只有在单独 recovered = true的时候 才会被移除,只要aborted= true,那么就不能允许panic从_panic链表移除掉。 总结 panic和defer一样都是挂接在协程上的,以链表的形式存在;panic会触发defer执行;当当前执行的defer触发源panic与当前执行的panic不一致的时候,便会将当前执行的defer的触发源panic进行关停aborted=true,且在最后协程输出的时候附带上aborted信息;recovered的作用是恢复panic;当被恢复panic后,会先删除触发的defer,并通过defer中的sp和pc找回到defer执行链表,恢复后,才会触发每次defer节点删除的检查 *** 作,将panic删除。 通过sp确认defer链表中属于当前函数的defer节点,并继续进行执行。aborted和recovered同时为true的时候,该panic节点不会被移除。最终输出panic信息的时候会将aborted和recovered信息附带上。最紧要的:panic的理解,需要结合defer来一起实用才会更佳哦浅谈defer。 参考
panic和recover;
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)