本次主要聊聊 Go 语言中关于 panic 和 recover 搭配使用 ,以及 panic 的基本原理
敖汉网站建设公司创新互联建站,敖汉网站设计制作,有大型网站制作公司丰富经验。已为敖汉数千家提供企业网站建设服务。企业网站搭建\外贸营销网站建设要多少钱,请找那个售后服务好的敖汉做网站的公司定做!
最近工作中审查代码的时候发现一段代码,类似于如下这样,将 recover 放到一个子协程里面,期望去捕获主协程的程序异常
图片
看到此处,是否会想这段代码在项目中是想当然写出来的吧,然而平日中,大多问题是出现在认知偏差上,那么本次,我们就来消除一下这个认知偏差
关于 Go 语言中显示的使用 panic 的地方不多,一般 panic ,基本上会出现在咱们程序出现异常退出的时候
例如访问了空指针里面的值,则会 panic 报错无效的内存地址,又例如访问量数组中不存在的数组所索引,或者切片索引,那么会报错 panic 数组越界等等
可是碰到这些 panic 的时候,实际上我们并不期望当前的服务直接挂掉,而是期望这个异常能够被识别,且不影响程序其他部分的模块运行
在 Go 中可以将 defer 和 recover 进行搭配使用,可以捕获和处理大部分的异常情况,例如可以这样
图片
这里可以看到,recover 捕获异常和发生异常的部分是在同一个协程中,实验证明是可以正常捕获并且处理异常
func main() {
log.SetFlags(log.Lshortfile)
panic("panic coming...")
}
图片
func main() {
log.SetFlags(log.Lshortfile)
if err := recover(); err != nil {
log.Println("recover panic : ", err)
}
panic("panic coming...")
}
图片
自然 recover 函数是在 panic 调用之前就已经执行,此时是还没有异常需要捕获和恢复的,待程序运行到 panic 处的时候,实际上并没有没有处理程序崩溃的异常
结果,仍然是程序崩溃
看了上述现象,实际上还是对知识点理解得不够,使用的时候想当然了,就像使用 defer 一样,如果对他不够了解的话,使用的时候,确实会出现一些奇奇怪怪的现象,对于 defer 的使用可以查看文末的文章地址
图片
注释中有说关于 panic 和 recover 的使用是作用于当前协程的,因此我们使用的时候,如果跨协程教程使用,自然不会达到我们期望的效果
图片
_panic 的结构如下:
type _panic struct {
argp unsafe.Pointer
arg interface{}
link *_panic
pc uintptr
sp unsafe.Pointer
recovered bool
aborted bool
goexit bool
}
上述两个结构表达的意思是,程序中出现 panic 的时候,实际上都会创建一个 _panic 结构,这个 _panic 结构里面存储了当前程序崩溃的一些必要信息,如下:
是一个 unsafe.Pointer 类型的成员,指向 defer 调用参数的指针
出现 panic 的原因,如果我们显示调用 panic,那么就是我们填入 panic 函数中的参数,例如上述的 panic coming ...
是一个指针,指向上一个,最近的一个 _panic 结构的地址,实际上此处就可以看到这个指针对应的是一个链表,一个又多个 _panic 结构组成的链表
图片
panic 是否已经处理完毕,即当前的这个 panic 是否是已经被 recover 了
表示当前的 panic 是否被中止
我们知道运行函数的时候需要入栈,运行完毕之后需要出栈
那么我们继续来阅读源码,上述看到 sp 和 pc ,那么我们就简单写一个 panic 的代码来看看汇编到底是怎么执行的,不用担心看不懂,我们只需要看关键词就行
还是上面的程序
图片
程序运行的时候可以执行 go tool compile -S main.go
可以看到汇编代码,可能其他的看不懂,但是我们可以看到如下关键词
图片
图片
代码中可以看到 p.recovered 逻辑下的关于 recover 的逻辑被删除掉了,在文章的后面会继续说到,当前我们先关注 panic 的事项
runtime.gopanic 程序的逻辑大体是这样的
Xdm 可以看上图,自己捋一捋逻辑就清晰了
接着,我们来看
图片
通过 runtime.gopanic 我们可以看到 fatalpanic 函数基本上就是做一个收尾工作了,如果上述程序处理完毕之后, fatalpanic 校验到 panic 是需要 recover 的,那么就打印 [recovered]
打印的这个信息是由 上图中 printpanics 完成的
图片
这下知道 panic 是如何去执行的了,那么对于现在来研究 recover 是如何落实的
还是同一个例子,咱们将 defer 部分的代码注打开,来继续看看效果
func main() {
log.SetFlags(log.Lshortfile)
defer func() {
if err := recover(); err != nil {
log.Println("recover panic : ", err)
}
}()
panic("panic coming...")
}
自然效果是我们期望的,捕获到了异常,且处理了
图片
继续打印汇编来查看一下关键词,是否有我们期望的函数出现
图片
图片
此处我们可以看到,实际 Go 中调用了多个函数
自然明眼人都看的出现,关键的函数实现自然是 runtime.gorecover ,那么我们来一探究竟
图片
查看源码我们可以知道, runtime.gorecover 实际上就是根据当前协程的 _panic 结构数据来判断是否需要恢复,如果需要则将 p.recovered = true
自然在这里将当前协程的数据修改掉,正是为了后续执行 runtime.gopanic 的时候提供保障, runtime.gopanic 执行的时候就会去判断和处理这个 p.recovered
前文中提到的关于 runtime.gopanic 中 处理 p.recovered 的逻辑是这样的
图片
图片
因此,当我们在同一个协程中出现了 panic,且在同一个协程中去使用 defer 来配合 recover 来进行捕获异常和处理异常,就可以得以实现,看到这里,有没有觉得还是蛮简单的,不就是去对一个 p.recovered 进行配合处理吗
自然,表面上是这样,其中对于寄存器的各种数据处理涉及的内容还是不少的,不过这不在我们今天聊的范畴中了
至此,相信你已经知道了这些
当前名称:Go语言中panic和recover搭配使用
文章地址:http://www.shufengxianlan.com/qtweb/news19/487069.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联