golang panic关键词执行原理与代码分析

news2024/10/5 15:34:34

使用的go版本为 go1.21.2

首先我们写一个简单的panic调度与捕获代码

package main

func 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:1035

type _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()
		}
	}

	// 获取当前的G
	gp := getg()

	// 判断当前M上运行的G是不是当前G
	if gp.m.curg != gp {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic on system stack")
	}

	// malloc过程中出现panic
	if 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 _panic
	p.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 = nil
			if !d.openDefer {
				d.fn = nil
				gp._defer = d.link
				freedefer(d)
				continue
			}
		}

		// 标记当前_defer为运行状态
		d.started = true

		// 记录_defer的panic
		d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

		done := true

		if d.openDefer { //如果_defer使用了 open-coded defers(编码的延迟调用)
			// 运行open-coded defer函数
			done = runOpenDeferFrame(d) //如果当前栈下面没有其他延迟函数,则返回true
			if done && !d._panic.recovered { //panic没有recover
				addOneOpenDeferFrame(gp, 0, nil)
			}
		} else {//执行对应方法
			//getargp返回其caller的保存callee参数的地址
			p.argp = unsafe.Pointer(getargp()) 
			d.fn()
		}
		p.argp = nil

		if gp._defer != d {
			throw("bad defer entry in panic")
		}
		d._panic = nil

		pc := d.pc
		sp := unsafe.Pointer(d.sp)
		if done { //将_defer从G的延迟链表移除,释放对应的_defer构造体资源
			d.fn = nil
			gp._defer = d.link
			freedefer(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._defer
			var prev *_defer
			if !done { //如果未执行完毕,跳过当前的帧直接执行下一个
				prev = d
				d = d.link
			}
			for d != nil {
				if d.started { //如果启动退出循环
					break
				}
				if d.openDefer { //如果使用了 open-coded defers
					if prev == nil { //将_defer从G的延迟链表移除释放_defer
						gp._defer = d.link
					} else {
						prev.link = d.link
					}
					newd := d.link
					freedefer(d)
					d = newd
				} else {
					prev = d
					d = d.link
				}
			}

			gp._panic = p.link //上面有对应的赋值,又重新赋了一遍没啥用
			for gp._panic != nil && gp._panic.aborted { //循环G中的_panic链表,去掉已经被标记中止的_panic
				gp._panic = gp._panic.link
			}
			if gp._panic == nil { // 如果当前G没有panic, 重置信号为0
				gp.sig = 0
			}
			// 将恢复帧发送给recovery.
			gp.sigcode0 = uintptr(sp)
			gp.sigcode1 = pc
			mcall(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() //获取当前G
	p := gp._panic // 从当前G中获取一个_panic
	// 如果G存在panic,它的状态不为中止,还未进行painc捕获,函数调用参数相同
	if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
		p.recovered = true
		return p.arg
	}

	return nil
}

总结

从上面的源码我们可以了解到panic的大致逻辑,当使用panic关键词时,将painc加入到G的_panic链表中去. 调度时 defer func() {recover()}(),会改写_painc中的recovered字段,可恢复的panic必须要recover的配合。 而且这个recover必须位于同一goroutine的直接调用链上,否则无法对 panic 进行恢复,未写完有些细节点还是没读懂,后续查阅资料补充。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1250145.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp视频倍速播放插件,uniapp视频试看插件——sunny-video使用文档

sunny-video视频倍速播放器 组件名:sunny-video 效果图 img1img2img3img4 平台差异说明 目前已应用到APP(安卓、iOS)、微信(小程序、H5)其它平台未测试 安装方式 本组件符合easycom规范,HBuilderX 2.5…

JoyT的科研之旅第一周——科研工具学习及论文阅读收获

CiteSpace概述 CiteSpace 是一个用于可视化和分析科学文献的工具,它专门针对研究者进行文献回顾和趋势分析。CiteSpace 的核心功能是创建文献引用网络,这些网络揭示了研究领域内各个文献之间的相互关系。使用 CiteSpace 可以为论文研究做出贡献的几种方…

【迅搜03】全文检索、文档、倒排索引与分词

全文检索、文档、倒排索引与分词 今天还是概念性的内容,但是这些概念却是整个搜索引擎中最重要的概念。可以说,所有的搜索引擎就是实现了类似的概念才能称之为搜索引擎。而且今天的内容其实都是相关联的,所以不要以为标题上有四个名词就感觉好…

NX二次开发UF_CURVE_ask_int_parms_sc 函数介绍

文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_int_parms_sc Defined in: uf_curve.h int UF_CURVE_ask_int_parms_sc(tag_t int_curve_object, int * num_objects_set_1, tag_t * * object_set_1, int * num_object…

LeetCode Hot100 102.二叉树的层序遍历

题目&#xff1a; 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 方法&#xff1a;迭代 class Solution {public List<List<Integer>> levelOrder(TreeNode root) {if …

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(七)

分页查询、删除和修改菜品 1. 菜品分页查询1.1 需求分析和设计1.1.1 产品原型1.1.2 接口设计 1.2 代码开发1.2.1 设计DTO类1.2.2 设计VO类1.2.3 Controller层1.2.4 Service层接口1.2.5 Service层实现类1.2.6 Mapper层 1.3 功能测试1.3.2 前后端联调测试 2. 删除菜品2.1 需求分析…

局域网协议:DNS(Domain Name System,域名系统)详解

文章目录 什么是DNS&#xff1f;DNS的重要性DNS的域名解析过程递归查询迭代查询 DNS解析失败怎么办&#xff1f;为什么DNS需要递归服务器&#xff1f;DNS为什么用UDP&#xff1f;推荐阅读 什么是DNS&#xff1f; DNS&#xff08;Domain Name System&#xff0c;域名系统&#…

【Unity3D】MAX聚合广告SDK——Pangle广告接入

Pangle, App Monetization Simplified 注册 登录 创建应用 创建广告单元 将其应用ID和广告ID关联到MAX广告。 下载Pangle Unity Plugin包&#xff0c;新建一个空工程&#xff08;很重要&#xff09; Unity版本2019.4.0f1 gradle plugin 4.2.0 gradle版本6.7.1 build_tools 34.…

问题汇总20231124

文章目录 1.练习题错题笔记&#xff1a;2. 串口配置中的无硬件控制流是什么3. 硬件控制流是如何管理数据流的&#xff1f;4. 串口不显示的原因有哪些&#xff1f;5. 中断服务函数中为什么一定要清除中断标志位&#xff1f;6. 中断标志位是什么时候设置的&#xff1f;7. BSRR8. …

探索优雅的处理 JavaScript 类数组对象的技巧

一. 引言 在 JavaScript 编程中&#xff0c;我们经常遇到类数组对象&#xff0c;它们拥有类似数组的结构和行为&#xff0c;但却不具备真正的数组方法和属性。常见的类数组对象包括 DOM 集合、函数的 arguments 对象和字符串等。如果我们想对这些类数组对象进行操作和处理&…

使用 STM32F7 和 TensorFlow Lite 开发低功耗人脸识别设备

本文旨在介绍如何使用 STM32F7 和 TensorFlow Lite框架开发低功耗的人脸识别设备。首先&#xff0c;我们将简要介绍 STM32F7 的特点和能力。接下来&#xff0c;我们将讨论如何使用 TensorFlow Lite 在 STM32F7 上实现人脸识别算法。然后&#xff0c;我们将重点关注如何优化系统…

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(八)

套餐模块功能开发 1. 新增套餐1.1 需求分析和设计1.1.1产品原型&#xff1a;1.1.2接口设计&#xff1a;1.1.3数据库设计&#xff1a; 1.2 代码开发1.2.1 DishController层1.2.2 DishService接口类1.2.3 DishServiceImpl接口实现类1.2.4 DishMapper层1.2.5 DishMapper.xml1.2.6 …

元宇宙现已开放!

在 2023 年 11 月 3 日 The Sandbox 首个全球创作者日上&#xff0c;The Sandbox 联合创始人 Arthur Madrid 和 Sebastien Borget 宣布元宇宙已开放&#xff0c;已创作完整体验的 LAND 持有者可以自行将体验发布至 The Sandbox 地图上。 精选速览 LAND 持有者&#xff1a;如果…

实验室信息管理系统源码,LIS系统源码,lis源码

医学检验(LIS)管理系统源码&#xff0c;云LIS系统全套商业源码 随着全自动生化分析仪、全自动免疫分析仪和全自动血球计数器等仪器的使用&#xff0c;检验科的大多数项目实现了全自动化分析。全自动化分析引入后&#xff0c;组合化验增多&#xff0c;更好的满足了临床需要&…

Android Frameworks 开发总结之七

1.修改android 系统/system/下面文件时权限不够问题 下面提到的方式目前在Bobcat的userdebug image上测试可行&#xff0c;还没有在user上测试过. 修改前: leifleif:~$ adb root restarting adbd as root leifleif:~$ adb disable-verity verity is already disabled using …

集「才华」与「美貌」于一身的原型设计利器—摹客RP

文章目录 画原型做设计&#xff0c;用摹客RP就够了 初遇摹客再遇摹客RP摹客RP简介与注册摹客RP的突出亮点1️⃣拥有海量矢量图标&#xff0c;满足各种设计场景2️⃣打造高扩展性组件&#xff0c;打破传统组件编辑模式3️⃣海量摹客RP模板例子随意挑选4️⃣实现多人实时协同&…

【数据结构】什么是栈?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f4cc;栈的定义 &#x1f4cc;元素进栈出栈的顺序 &#x1f4cc;栈的抽象数据类型 &#x1f4cc;栈的顺序存储结构 &#x1f4cc;栈的链式存储结构 链栈的进…

micro_ros需要用到的hardware

我没有那么长的线啊&#xff0c;所以就用一个4块5的usb转串口看看 没有那么高档的开发板&#xff0c;就用主流的STM32F103C8T6试试看 这应该就是个仿真器了&#xff0c;一个字不认得都能够看的出来吧

《尚品甄选》:后台系统——权限管理之角色管理(debug一遍)

文章目录 一、权限管理介绍二、表结构的设计三、查询角色四、添加角色五、修改角色六、删除角色 一、权限管理介绍 在后台管理系统中&#xff0c;权限管理是指为了保证系统操作的安全性和可控性&#xff0c;对用户的操作权限进行限制和管理。简单的来说就是某一个用户可以使用…

【开源】基于JAVA的计算机机房作业管理系统

项目编号&#xff1a; S 017 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S017&#xff0c;文末获取源码。} 项目编号&#xff1a;S017&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 登录注册模块2.2 课程管理模块2.3 课…