golang panic关键词执行原理与代码分析
使用的go版本为 go1.21.2
首先我们写一个简单的panic调度与捕获代码
package mainfunc main() {defer func() {recover()}()panic("panic test")
}
通过go build -gcflags -S main.go获取到对应的汇编代码
可以看到当我们调度panic时,Go的编译器会将这段代码翻译为CALL runtime.gopanic(SB)
我们先来看一下panic构造体的底层源码
panic源码与解读
//代码位置 $GOROOT/src/runtime/runtime2.go L:1035type _panic struct {argp unsafe.Pointer // 指向在 panic 运行期间执行的延迟调用参数的指针,不可移动 - liblink 工具已知其位置arg any // 参数link *_panic // panic链表pc uintptr // 返回到运行时的位置sp unsafe.Pointer // 返回到运行时的栈指针位置recovered bool // 是否已被恢复aborted bool // 是否已被中止goexit bool // 是否执行了 Goexit 函数
}
gopanic源码与解读
//代码位置 $GOROOT/src/runtime/panic.go L:826
// 实现预声明函数 panic
func gopanic(e any) {// 处理异常参数为 nil 的情况if e == nil {// 如果 debug.panicnil 不等于 1,将e设置为PanicNilError类型//if debug.panicnil.Load() != 1 {e = new(PanicNilError)} else {panicnil.IncNonDefault()}}// 获取当前的Ggp := getg()// 判断当前M上运行的G是不是当前Gif gp.m.curg != gp {print("panic: ")printany(e)print("\n")throw("panic on system stack")}// malloc过程中出现panicif gp.m.mallocing != 0 {print("panic: ")printany(e)print("\n")throw("panic during malloc")}// 禁止抢占的情况下执行 panic (!="" 保持当前G在这M运行)if gp.m.preemptoff != "" {print("panic: ")printany(e)print("\n")print("preempt off reason: ")print(gp.m.preemptoff)print("\n")throw("panic during preemptoff")}// 当初M处于锁的状态if gp.m.locks != 0 {print("panic: ")printany(e)print("\n")throw("panic holding locks")}// 定义一个panic变量var p _panicp.arg = e //这个e 就是我们panic("xxxx") 里面写的东西//将这个panic加入到G的_panic链表中去p.link = gp._panic gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) // 增加运行panic延迟计数runningPanicDefers.Add(1)// 计算 getcallerpc/getcallersp,以避免扫描 gopanic 帧addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))for {//逐步获取当前G中的defer调用d := gp._defer// 如果获取到的构造体为空,直接返回。if d == nil {break}// 如果当前_defer运行,将_defer从G的延迟链表移除,释放对应的_defer构造体资源,防止重复执行if d.started {if d._panic != nil {d._panic.aborted = true}d._panic = nilif !d.openDefer {d.fn = nilgp._defer = d.linkfreedefer(d)continue}}// 标记当前_defer为运行状态d.started = true// 记录_defer的panicd._panic = (*_panic)(noescape(unsafe.Pointer(&p)))done := trueif d.openDefer { //如果_defer使用了 open-coded defers(编码的延迟调用)// 运行open-coded defer函数done = runOpenDeferFrame(d) //如果当前栈下面没有其他延迟函数,则返回trueif done && !d._panic.recovered { //panic没有recoveraddOneOpenDeferFrame(gp, 0, nil)}} else {//执行对应方法//getargp返回其caller的保存callee参数的地址p.argp = unsafe.Pointer(getargp()) d.fn()}p.argp = nilif gp._defer != d {throw("bad defer entry in panic")}d._panic = nilpc := d.pcsp := unsafe.Pointer(d.sp)if done { //将_defer从G的延迟链表移除,释放对应的_defer构造体资源d.fn = nilgp._defer = d.linkfreedefer(d)}if p.recovered { //panic已经恢复gp._panic = p.link if gp._panic != nil && gp._panic.goexit && gp._panic.aborted {// A normal recover would bypass/abort the Goexit. Instead,// we return to the processing loop of the Goexit.gp.sigcode0 = uintptr(gp._panic.sp)gp.sigcode1 = uintptr(gp._panic.pc)mcall(recovery)throw("bypassed recovery failed") // mcall should not return}runningPanicDefers.Add(-1)// 从G中获取一个_defer构造体d := gp._defervar prev *_deferif !done { //如果未执行完毕,跳过当前的帧直接执行下一个prev = dd = d.link}for d != nil {if d.started { //如果启动退出循环break}if d.openDefer { //如果使用了 open-coded defersif prev == nil { //将_defer从G的延迟链表移除释放_defergp._defer = d.link} else {prev.link = d.link}newd := d.linkfreedefer(d)d = newd} else {prev = dd = d.link}}gp._panic = p.link //上面有对应的赋值,又重新赋了一遍没啥用for gp._panic != nil && gp._panic.aborted { //循环G中的_panic链表,去掉已经被标记中止的_panicgp._panic = gp._panic.link}if gp._panic == nil { // 如果当前G没有panic, 重置信号为0gp.sig = 0}// 将恢复帧发送给recovery.gp.sigcode0 = uintptr(sp)gp.sigcode1 = pcmcall(recovery)throw("recovery failed") // mcall should not return}}// 没有更多的延迟调用,现在采用传统的 panic 方式// 由于在冻结世界之后调用任意用户代码是不安全的,// 我们调用 preprintpanics 来调用所有必要的 Error// 和 String 方法,以在 startpanic 之前准备好 panic 字符串。preprintpanics(gp._panic)fatalpanic(gp._panic) //触发致命的 panic*(*int)(nil) = 0 //为了消除编译器的错误提示
}
当我们调度recover时,Go的编译器会将这段代码翻译为CALL runtime.gorecover(SB)
gorecover源码与解读
//代码位置 $GOROOT/src/runtime/panic.go L:1045
func gorecover(argp uintptr) any {gp := getg() //获取当前Gp := gp._panic // 从当前G中获取一个_panic// 如果G存在panic,它的状态不为中止,还未进行painc捕获,函数调用参数相同if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {p.recovered = truereturn p.arg}return nil
}
总结
从上面的源码我们可以了解到panic的大致逻辑,当使用panic关键词时,将painc加入到G的_panic链表中去. 调度时 defer func() {recover()}(),会改写_painc中的recovered字段,可恢复的panic必须要recover的配合。 而且这个recover必须位于同一goroutine的直接调用链上,否则无法对 panic 进行恢复,未写完有些细节点还是没读懂,后续查阅资料补充。
相关文章:
golang panic关键词执行原理与代码分析
使用的go版本为 go1.21.2 首先我们写一个简单的panic调度与捕获代码 package mainfunc main() {defer func() {recover()}()panic("panic test") }通过go build -gcflags -S main.go获取到对应的汇编代码 可以看到当我们调度panic时,Go的编译器会将这段…...
Error running Tomcat8: Address localhost:1099 is already in use 错误解决
摘要: 有时候运行web项目的时候会遇到 Error running Tomcat8: Address localhost:1099 is already in use 的错误,导致web项目无法运行。这篇 blog 介绍了解决办法。 有时候运行web项目的时候会遇到 Error running Tomcat8: Address localhost:1099 is already in …...
android studio如何给安卓虚拟机发送短信
首先,cd到指定路径 默认情况下,Android SDK通常安装在以下位置: Windows:C:\Users\YourUsername\AppData\Local\Android\Sdk\platform-toolsmacOS:/Users/YourUsername/Library/Android/sdk/platform-toolsLinux&…...
立体仓库PLC控制系统子站诊断功能块
// //获取profinet网络已组态站信息 // //MODE:0自动辨识是获取组态信息还是错误信息 //MODE:1获取IO 设备从站已组态 //MODE:2获取IO 设备 从站故障 //MODE:3获取IO 设备 从站已禁用 //MODE:4获取IO 设备 从站存在 //MODE:5获取IO 设备 从站出现问题 // //站点状态字节位含义 …...
NFT Insider115:The Sandbox开设元宇宙Diorama快闪店,YGG Web3 游戏峰会已开幕
引言:NFT Insider由NFT收藏组织WHALE Members、BeepCrypto联合出品,浓缩每周NFT新闻,为大家带来关于NFT最全面、最新鲜、最有价值的讯息。每期周报将从NFT市场数据,艺术新闻类,游戏新闻类,虚拟世界类&#…...
【Redis篇】简述Java中操作Redis的方法
文章目录 🎄简述Jedis🎄Jedis优点🍔使用Jedis连接Redis⭐进行测试🎈进行测试 Redis(Remote Dictionary Server)是一种流行的高性能内存数据库,广泛应用于各种应用程序和系统中。作为Java开发人员…...
深度解读英伟达新一轮对华特供芯片H20、L20、L2的定位
大家好,我是极智视界,欢迎关注我的公众号,获取我的更多前沿科技分享 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0aiNxERDq 因为一直从事 AI 工…...
一起学docker系列之九docker运行mysql 碰到的各种坑及解决方法
目录 前言1 Docker 运行mysql命令2 坑一:无法读取/etc/mysql/conf.d目录的问题3 坑二:/tmp/ibnr0mis 文件无法创建/写入的问题4 坑三:Navicat 连接错误(1045-access denied)5 坑四:MySQL 登录失败问题结语 …...
利用Nginx与php处理方式不同绕过Nginx_host实现SQL注入
目录 首先需要搭建环境 nginxphpmysql环境: 搭建网站 FILTER_VALIDATE_EMAIL 绕过 方法1:冒号号分割host字段 方法2:冒号号分割host字段 方法3:SNI扩展绕过 首先需要搭建环境 nginxphpmysql环境: php安装包&a…...
分割list 批量插入数据指定条数数据
一、代码层面切割好list,然后插入 // package org.apache.commons.collections4; 先将list切成1000条一份 List<List<DeptDO>> p1 ListUtils.partition(deptList, 1000); for (List<DeptDO> deptDOS : p1) { // 1000条一次批量插入systemDeptMa…...
Arduino库之 LedControl 库说明文档
LedControl 库最初是为基于 8 位 AVR 处理器的 Arduino 板编写的。用于通过MAX7219芯片控制LED矩阵和7段数码管。但由于该代码不使用处理器的任何复杂的内部功能,因此具有高度可移植性,并且应该在任何支持 和 功能的 Arduino(类似)…...
Hadoop学习总结(MapReduce的数据去重)
现在假设有两个数据文件 file1.txtfile2.txt2018-3-1 a 2018-3-2 b 2018-3-3 c 2018-3-4 d 2018-3-5 a 2018-3-6 b 2018-3-7 c 2018-3-3 c2018-3-1 b 2018-3-2 a 2018-3-3 b 2018-3-4 d 2018-3-5 a 2018-3-6 c 2018-3-7 d 2018-3-3 c 上述文件 file1.txt 本身包含重复数据&…...
ctfshow sql
180 过滤%23 %23被过滤,没办法注释了,还可以用’1’1来闭合后边。 或者使用--%0c-- 1%0corder%0cby%0c3--%0c--1%0cunion%0cselect%0c1,2,database()--%0c--1%0cunion%0cselect%0c1,2,table_name%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_…...
Java实现求最大值
1 问题 接收用户输入的3个整数,如何将最大值作为结果输出。 2 方法 采用“截图文字代码”的方式描述。 引入输入包调用main()函数,提示并接收用户输入的3个整数,并交由变量a b c来保存。对接收的3个数据进行比较,先比较a和b&#…...
NX二次开发UF_CURVE_ask_curve_inflections 函数介绍
文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_curve_inflections Defined in: uf_curve.h int UF_CURVE_ask_curve_inflections(tag_t curve_eid, double proj_matrx [ 9 ] , double range [ 2 ] , int * num_infpt…...
一个基于RedisTemplate静态工具类
每次是用RedisTemplate的时候都需要进行自动注入实在是太麻烦了,于是找到一个讨巧的办法。 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.…...
【计算机网络笔记】数据链路层——差错编码
系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…...
js生成pdf并自动上传
1.生成pdf前要让js选中生成pdf部分的dom <div id"printPageFirst"> pdf内容区 </div> 2.使用两个插件,import到项目里,然后是获取dom进行生成pdf操作 import html2canvas from html2canvas import JsPDF from jspdf function cr…...
高品质MP3音频解码语音芯片WT2003Hx的特征优势与应用场景
在现代化科技快速发展的时代,高品质音频语音芯片在各个领域的应用越来越广泛。唯创知音推出的高品质MP3音频语音芯片WT2003Hx,凭借其出色的特性与优势,赢得了市场的广泛认可。本文将详细介绍WT2003Hx的特征优势以及其在各个领域的应用场景。 …...
浅析linux中的信号
人们往往将信号称为“软件中断”,它提供了异步事件的处理机制,这些事件可以来自系统外部(如用户按下ctrlc产生中断符),也可能来自程序或者内核内部的执行动作(如进程除零操作)。进程收到信号&am…...
当大模型开始控制设备:我是怎么理解 Agent 架构的
一、前言:什么是 OFA VQA 模型? OFA(One For All)是字节跳动提出的多模态预训练模型,支持视觉问答、图像描述、图像编辑等多种任务,其中视觉问答(VQA)是最常用的功能之一——输入一张…...
Java开发者AI转型第六课!Spring AI 灵魂架构 Advisor 切面拦截与自定义实战
大家好,我是直奔標杆!欢迎各位Java同行来到《Spring AI 零基础到实战》专栏的第六课,咱们继续并肩前行,一起攻克Spring AI的核心知识点~在前五节课的学习中,咱们一步步让AI拥有了专属人设、实现了图片识别、…...
别再复制粘贴了!用STM32CubeMX LL库玩转按键、LED和蜂鸣器,这才是高效开发
STM32CubeMX LL库实战:解锁寄存器级高效开发的5个关键策略 当你第一次在STM32项目中使用HAL库时,可能会被它的易用性所吸引——简单的API调用就能完成复杂的外设配置。但当你深入项目开发,特别是对性能有严格要求时,HAL库的抽象层…...
HPH构造核心图解
HPH的构造乃是理解其高效运行的关键基础所在,它对设备于高压环境下的稳定性以及寿命起着决定性的作用。从宏观的整体布局一直到微观的内部组件,其中的每一个细节均与密封性能以及能量转换效率息息相关。 HPH的核心部件有哪些 HPH主要由泵体、转子组件、定…...
苹果2026高层大换血:库克转任执行董事长,新帅能否带领苹果再创新高?
苹果2026年高层调整4月20日,苹果正式宣布,Tim Cook将于2026年9月1日转任执行董事长,现任硬件工程负责人John Ternus接任CEO;同一天,Johny Srouji也被任命为首席硬件官。这些年,围绕库克该不该退的争议不断&…...
python游戏开发和设计学习总结
在本次 Python 课程中,我们主要学习了弹球游戏和飞机大战两款小游戏的开发与设计,借助 pygame 库完成代码编写、功能设计与调试,从基础小游戏入手,逐步了解 2D 游戏的开发流程,收获了很多编程与游戏设计的知识。 一、…...
别再搞混了!博图SCL实现FIFO时,数组越界和逆序输出的两个关键坑点解析
博图SCL实现FIFO时数组越界与逆序输出的深度避坑指南 在工业自动化编程中,FIFO(先进先出)队列是最基础也最常用的数据结构之一。许多TIA Portal开发者在使用SCL语言实现FIFO功能时,往往会在两个关键环节栽跟头:数组索引…...
OpenCV主库与Contrib扩展版本匹配避坑指南:以Ubuntu 22.04 + OpenCV 4.5.5为例
OpenCV主库与Contrib扩展版本匹配避坑指南:以Ubuntu 22.04 OpenCV 4.5.5为例 在计算机视觉开发中,OpenCV无疑是最受欢迎的库之一。然而,许多开发者在尝试扩展OpenCV功能时,常常会遇到一个看似简单却令人头疼的问题:主…...
Ubuntu 18.04 + CUDA 9.0 环境下,保姆级避坑指南:从源码编译GCC 4.9.2到成功运行DensePose
Ubuntu 18.04 CUDA 9.0 环境下GCC 4.9.2源码编译全攻略:破解DensePose安装的核心难题 在计算机视觉领域,DensePose作为将2D图像中的人体像素映射到3D表面模型的重要工具,其安装过程却常常让开发者望而生畏。特别是在Ubuntu 18.04和CUDA 9.0环…...
STM32无源蜂鸣器进阶玩法:基于MIDI协议的音乐播放器设计与实现
STM32无源蜂鸣器进阶玩法:基于MIDI协议的音乐播放器设计与实现 在嵌入式开发中,让硬件"唱出"动听旋律总是一件令人兴奋的事情。不同于简单的固定曲目播放,基于MIDI协议的音乐播放系统为STM32开发者提供了更专业的音频控制方式。本文…...
