自学内容网 自学内容网

Go-知识recover

1. 介绍

内置函数recover用于消除panic并使程序恢复正常。

recover的返回值是什么?
执行recover之后程序将从哪里继续运行?
recover为什么一定要在defer中使用?

2. 工作机制

2.1 recover 定义

与panic函数一样,recover函数也是一个内置函数
在这里插入图片描述

recover函数的返回值就是panic函数的参数,当程序产生panic时,recover函数就可用于消除panic,
同时返回panic函数的参数,如果程序没有发生panic,则recover函数返回nil.
如果panic函数参数为nil,那么仍然是一个有效的panic,此时recover函数仍然可以捕获panic,但返回值为nil。
recover函数必须且直接位于defer函数中才有效。
在这里插入图片描述

即使在defer函数中调用func,func中的recover也无法消除panic,必须是直接的defer中才行,而且不能嵌套:
在这里插入图片描述

2.2 工作流程

出现panic后,recover可以恢复程序正常的执行流程
在这里插入图片描述

黑色箭头代表征程执行流程,红色箭头表示出现了panic的执行流程。
当出现panic后,panic后续的代码不在被执行。
逐层执行defer,直到recover消除,或这到达最外层。
如果panic被recover了,那么别的goroutine是不会感知panic的。

2.3 总结

recover的几个要点:

  • recover函数调用必须要位于defer函数中,且不能出现在另一个嵌套函数中
  • recover函数成功处理异常后,无法再次回到本函数发生panic的位置上继续执行
  • recover函数可以消除本函数产生或收到的panic,上游函数感知不到panic的发生

当函数中发生panic并用recover函数恢复后,当前函数仍然会继续返回,对于匿名函数返回值,函数将返回相应类型的零值,对于具名返回值,
函数将返回当前已经存在的值。

3. 原理

3.1 recover函数的真正逻辑

recover内置函数实际上调用的是gorecover函数
src/runtime2.go
在这里插入图片描述

当panic不为空,而且panic没有被捕获,而且recover必须被defer直接调用,才会进行捕获处理。

3.2 恢复逻辑

gorecover函数通过协程数据结构中的_panic得到当前panic实例,如果当前panic的状态支持recover,则给该panic实例标记recoverd状态,
最后返回panic函数的参数。
当前执行的recover函数的defer函数是被gopanci执行的,defer函数执行结束后,在gopanic中会检查_panic的recoverd的状态,如果发现panic被恢复,
则gopanic将结束当前panic流程,将程序流程回复正常。
在这里插入图片描述

3.3 生效条件

if p != nil && !p.recovered && argp == uintptr(p.argp) {
p.recovered = true    
return p.arg
}

当前协程没有产生panic时,协程结构体中_panic的链表为空,也就是 pnil,不满足恢复条件。
假设函数包含多个defer函数,前面的defer函数通过recover函数消除panic后,函数中剩余的defer仍然会执行,但不能再次recover。也就是p.recoverd
true
内置函数recover没有参数,但是gorecover函数却有参数,gorecover中的参数为调用recover函数的参数地址,通常是defer 函数的参数地址。
_panic实例中也保存了当前defer函数的参数地址,如果二者一致,说明recover被defer函数直接调用。
在这里插入图片描述

func TestTwelve(t *testing.T) {
defer func() { // 假设函数为 A
func() { // 假设函数为 B
// gorecover(B)
// (argp = B) != A
if err := recover(); err != nil {
fmt.Println(err)
}
}()
}()
panic("panic")
}

在这里插入图片描述

4. 总结

4.1 recover的返回值是什么?

recover的返回值是panic的参数。

4.2 执行recover之后程序将从哪里继续运行?

将从defer中继续执行(不管是否发生panic,defer 都会执行)

4.3 recover为什么一定要在defer中使用?

如果recover函数不在defer函数中,那么recover函数可能出现在panic之前,也可能出现在panic之后。
出现在panic之前,因为找不到panic实例而无法生效。
出现在panic之后,代码没有机会执行,所以recover函数必须存在于defer函数中才会生效。

4.4 recover为什么必须被defer直接调用才生效

这个问题等价于:
为什么在defer函数中调用了函数A ,在A中的recover不会生效
为什么recover只能处理本函数中或者传递给本函数的panic

func TestTwelve(t *testing.T) {
defer func() { 
thirdPkg.Clean() // 调用第三方清理
}()
panic("panic")
}

有时我们会在代码里显式的触发panic,同时还往往会在defer函数里调用第三方的包清理资源,如果第三方包也使用了recover,那么我们触发的panic将会被拦截,
而且这种拦截可能是非预期。所以recover会被限定范围只能是defer直接调用才行。


原文地址:https://blog.csdn.net/a18792721831/article/details/142415248

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!