golang关于panic的解析

golang关于panic的解析,第1张

panic解析:

关于panic,接下来主要讲panic的执行机制和顺序,我会通过多个例子来讲解不同情况下panic逻辑的处理流程。

首先这是panic的结构体

与defer是相辅相成的:

首先,通过上一节defer的讲解我们知道,多个defer组成列表挂在当前协程goroutine的成员变量_defer字段下的。
同理在goroutine结构体上还有个字段是用来挂接多个_panic结构体的。

panic与defer的联动 简单例子
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
如图

最终deferA执行完毕且deferA节点被移除后,协程输出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

基于例子二,我在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;

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/996224.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存