关于 panic 的时机,在上篇 姿势篇 我们已经学习到 panic 有三种诞生方式:三种都归一到panic( ) 函数的调用,指出 Go 的 panic 只是一个特殊的函数调用,是语言层面的处理。知道了 panic 是怎么来的,下一步就该了解 panic 怎么去的?初学 Go 的时候,奇伢心里也常常有些疑问:今天深入到代码原理,明确以上问题。
Go 源码版本声明: Go 1.13.5
看看_panic 的数据结构:重点字段关注:再看一下 goroutine 的两个重要字段:从这里我们看出:_defer 和 _panic 链表都是挂在 goroutine 之上的。什么时候会导致_panic 链表上多个元素?panic( ) 的流程下,又调用了 panic( ) 函数。这里有个细节要注意了,怎么才能做到panic( ) 流程里面再次调用 panic( ) ?划重点:只能是在 defer 函数上,才有可能形成一个 _panic 链表。因为 panic( ) 函数内只会执行 _defer 函数!
recover 函数:为了方便讲解,我们由简单的开始分析,先看 recover 函数究竟做了什么?recover 对应了 runtime/panic.go 中的 gorecover 函数实现。这个函数可太简单了:这就是 recover 函数的全部内容,只给_panic.recovered 赋值而已,不涉及代码的神奇跳转。而 _panic.recovered 的赋值是在 panic 函数逻辑中发挥作用。
panic 函数:panic 的实现在一个叫做 gopanic 的函数,位于runtime/panic.go 文件。gopanic 函数:panic 机制最重要最重要的就是 gopanic 函数了,所有的 panic 细节尽在此。为什么 panic 会显得晦涩,主要有两个点:上面逻辑可以拆分为循环内和循环外两部分去理解:for 循环内:循环内做的事情可以拆解成:那些思考问题:你会发现,更改recovered 这个字段的时机只有在第三个步骤的时候。在任何地方,你都改不到 _panic.recovered 的值。问题一:为什么 recover 一定要放在 defer 里面才生效?因为,这是唯一的时机!问题二:为什么 recover 已经放在 defer 里面,但是进程还是没有恢复?回忆一下上面 for 循环的操作:划重点:在 gopanic 里,只遍历执行当前 goroutine 上的 _defer 函数链条。所以,如果挂在其他 goroutine 的 defer 函数做了 recover ,那么没有丝毫用途。问题三:为什么 panic 之后,还能再 panic ?有啥影响?这个其实很容易理解,有些童鞋可能想复杂了。gopanic 只是一个函数调用而已,那函数调用为啥不能嵌套递归?当然可以。触发的场景一般是:这不就有了嘛,就是个简单的函数嵌套而已,没啥不可以的,并且在这种场景下,_panic 结构体就会从 gp._panic 开始形成了一个链表。而gopanic 函数指令执行的特殊在于两点:举个嵌套的例子:函数执行:再看一个栗子:上面的函数会出打印堆栈退出进程吗?答案是:不会。 猜一下输出是啥?终端输出结果如下:你猜对了吗?奇伢给你梳理了一下完整的路线:再来一个对比的例子:上面的函数会打印堆栈,并且退出吗?**答案是:会。**输出如下:执行路径如下:你猜对了吗?
recovery 函数:最后,看一下关键的 recovery 函数。在gopanic 函数中,在循环执行 defer 函数的时候,如果发现 _panic.recovered 字段被设置成 true 的时候,调用 mcall(recovery) 来执行所谓的恢复。看一眼recovery 函数的实现,这个函数极其简单,就是恢复 pc,sp 寄存器,重新把 Goroutine 投递到调度队列中。重置了 pc,sp 寄存器代表什么意思?pc 寄存器指向指令所在的地址,换句话说,就是跳转到其他地方执行指令去了。而不是顺序执行 gopanic 后面的指令了,补回来了。_defer.pc 的指令行执行代码,这个指令是哪里?这个要回忆一下defer 的章节,defer 注册延迟函数的时候对应一个 _defer 结构体,在 new 这个结构体的时候,_defer.pc 字段赋值的就是 new 函数的下一行指令。这个在 深入剖析 defer 篇 详细说过。举个例子,如果是栈上分配的话,那么在deferprocStack ,所以,mcall(recovery) 跳转到这个位置,其实后续就走 deferreturn 的逻辑了,执行后续的 _defer 函数链。本次 panic 就到此为止,相当于就恢复了程序的正常运行。当然,如果后续在 defer 函数里面又出现 panic ,那可能形成一个_panic 的链条,但是每一个的处理还是一样的。划重点:函数的 call,ret 是最常见的指令跳转。最本源的就是 pc 寄存器,函数压栈,出栈的时候,修改的也是 pc 寄存器,在 recovery 流程里,则来的更直接一点,直接改 pc ,sp。
for 循环外:走到 for 循环外,那程序 100% 要退出了。因为 fatalpanic 里面打印一些堆栈信息之后,直接调用 exit 退出进程的。到这已经没有任何机会了,只能乖乖就义。退出的调用就在fatalpanic 里:所以这个问题清楚了嘛:为什么 panic 会让 Go 进程退出的 ?因为调用了 exit(2) 嘛。
总结后记:panic 就是一个特殊的函数调用,没啥特殊的。只所以特殊,是因为有一些特殊的指令跳转而已。原创不易,欢迎关注:奇伢云存储
本文地址: http://www.goggeous.com/20250107/1/1277599
文章来源:天狐定制
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。
2025-01-08职业培训
2025-01-08职业培训
2025-01-08职业培训
2025-01-08职业培训
2025-01-08职业培训
2025-01-08职业培训
2025-01-08职业培训
2025-01-08职业培训
2025-01-08职业培训
2025-01-08职业培训
2025-01-07 04:25:02职业培训
2025-01-07 04:25:02职业培训
2025-01-07 04:24:54职业培训
2025-01-07 04:24:53职业培训
2025-01-07 04:24:53职业培训
2025-01-07 04:24:52职业培训
2025-01-07 04:24:51职业培训
2025-01-07 04:24:46职业培训
2025-01-07 04:24:44职业培训
2025-01-07 04:24:35职业培训
2024-12-10 11:06职业培训
2024-12-14 23:49职业培训
2024-12-05 10:06职业培训
2025-01-02 13:26职业培训
2024-12-13 23:09职业培训
2024-12-23 12:39职业培训
2024-11-27 06:53职业培训
2024-12-06 03:39职业培训
2024-12-01 12:23职业培训
2024-11-27 00:21职业培训
扫码二维码
获取最新动态