Go-知识panic

news2024/11/25 7:52:43

Go-知识panic

  • 1. 介绍
  • 2. 工作机制
    • 2.1 panic函数
    • 2.2 工作流程
    • 2.3 总结
  • 3. 原理
    • 3.1 数据结构
    • 3.2 gopanic
      • 没有defer
      • defer函数处理
      • 嵌套defer
  • 4. 总结

Go-知识error :https://blog.csdn.net/a18792721831/article/details/140430350
Go-知识defer : https://blog.csdn.net/a18792721831/article/details/140734394?spm=1001.2014.3001.5501

1. 介绍

Go 语言开发中程序出现错误,比较常见的是返回error给调用者,但是对于危险的操作,比如内存越界访问等,程序会触发panic,提前结束程序运行。
同样是退出程序,与os.Exit相比,panic的退出方式比较优雅,panic会做一定的善后操作(处理defer函数),并且支持使用recover消除panic。
defer,panic和recover经常会相互作用。

2. 工作机制

程序发生panic时会结束当前协程,进而触发整个程序的崩溃。内置函数recover可以接收panic并使程序重新回到正轨。

触发panic函数中的defer语句是否会执行?
上游函数中的defer语句是否会执行?
其他协程中的defer语句是否会执行?
defer语句中产生panic会发生什么?

2.1 panic函数

panic是一个内置函数
在这里插入图片描述

它接收一个任意类型的参数,参数将在程序崩溃时通过另一个内置函数print打印出来,如果程序返回途中任意一个defer函数执行了recover,
那么该参数也是recover的返回值。
panic可由程序员显示触发,Go运行时遇到如内存越界之类的问题时也会触发。

2.2 工作流程

在不考虑recover的情况下,panic的执行如下:
在这里插入图片描述

在上面的流程中,黑色箭头代表程序的正常执行流程,红色箭头代表panic的执行流程。
程序启动了两个协程,如果某协程执行过程中产生了panic,那么程序将立即转向执行defer函数,当函数中的defer执行完毕后继续处理上层函数的defer,
当协程中所有defer处理完后,程序退出。
在panic的执行过程中有几点:

  • panic会递归执行协程中所有的defer,与函数正常退出时的执行顺序一致
  • panic不会处理其他协程中的defer
  • 当前协程中的defer处理完成后,触发程序退出

如果panic在执行过程中(defer函数中)再次发生panic,程序将立即中止当前defer函数的执行,然后继续接下来的panic的流程,只是当前defer函数中panic后面的
语句就没有机会执行了。
如果在panic的执行过程中任意一个defer函数执行了recover,那么panic的处理流程就会中止。

2.3 总结

panic触发异常,如果函数没有处理异常,则异常将沿函数调用链逐层向上传递,最终导致程序退出。
每个协程中都维护了一个defer链表,执行过程中每遇到一个defer语句都创建_defer实例并插入链表,函数退出时取出本函数创建的_defer实例并执行。
panic发生时,实际上是把程序流程转向了这个_defer链表,当链表中的defer函数执行完,触发程序退出。

3. 原理

3.1 数据结构

src/runtime2.go中定义了_panic的数据结构
在这里插入图片描述

当panic运行时,会逐个处理_defer,并且会把当前_panic指针传入_defer中,这样当defer函数中产生新的panic是,会将原panic标记为aborted。
_defer函数中的recover会吧panic标记为recoverd
_panic和_defer都存储在协程数据结构中
在这里插入图片描述

3.2 gopanic

panic最终执行的是gopanic函数

func gopanic(e interface{}) {
    // 获取 goroutine 的信息
	gp := getg()
	if gp.m.curg != gp {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic on system stack")
	}
	// 内存分配的时候出现panic,也就是内存越界
	if gp.m.mallocing != 0 {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic during malloc")
	}
	if gp.m.preemptoff != "" {
		print("panic: ")
		printany(e)
		print("\n")
		print("preempt off reason: ")
		print(gp.m.preemptoff)
		print("\n")
		throw("panic during preemptoff")
	}
	// 处理锁的时候的panic
	if gp.m.locks != 0 {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic holding locks")
	}
	// 创建 _panic 实例
	var p _panic
	p.arg = e
	p.link = gp._panic
	gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
	// 开始处理defer的标记
	atomic.Xadd(&runningPanicDefers, 1)
	// 循环执行defer
	for {
	    // 获取_defer
		d := gp._defer
		// 如果_defer没有了,那么跳出循环
		if d == nil {
			break
		}
		// 如果是在执行_defer的时候产生了panic,也就是在defer中,再次panic
		if d.started {
		    // 如果defer 的panic不为空,表示在处理defer的panic的defer的时候,又产生了panic,也就是嵌套的时候
		    // 将嵌套的panic设置为aborted
			if d._panic != nil {
				d._panic.aborted = true
			}
			// 否则清空之前的panic
			d._panic = nil
			d.fn = nil
			// 开始执行后续的defer
			gp._defer = d.link
			// 释放当前defer
			freedefer(d)
			continue
		}

		// 标记defer开始执行(panic后执行defer,不嵌套)
		d.started = true
		// 将_panic传给_defer,用于recover处理
		d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
		// panic的参数
		p.argp = unsafe.Pointer(getargp(0))
		// 执行defer
		reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
		// 执行完成后,回收 _defer实例
		p.argp = nil
		// reflectcall did not panic. Remove d.
		if gp._defer != d {
			throw("bad defer entry in panic")
		}
		d._panic = nil
		d.fn = nil
		// 指向下一个defer
		gp._defer = d.link
		pc := d.pc
		sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
		// 清理_defer, heap-allocated,stack-allocated
		freedefer(d)
		// 如果在defer中有recover捕获了panic
		if p.recovered {
		    // 退出panic
			atomic.Xadd(&runningPanicDefers, -1)
			// 移动_panic到下一个
			gp._panic = p.link
			// 如果panic被中断了,跳过
			for gp._panic != nil && gp._panic.aborted {
				gp._panic = gp._panic.link
			}
			// panic列表空
			if gp._panic == nil { // must be done with signal
			    // 设置信号
				gp.sig = 0
			}
			gp.sigcode0 = uintptr(sp)
			gp.sigcode1 = pc
			// 执行recover
			mcall(recovery)
			throw("recovery failed") // mcall should not return
		}
	}
	// 打印panic,如果panic是error,调用Error方法获取异常信息,否则调用String方法获取描述
	preprintpanics(gp._panic)
	// 中止程序
	fatalpanic(gp._panic) // should not return
	*(*int)(nil) = 0      // not reached
}

没有defer

gopanic函数首先会创建一个_panic实例,并将其保存在协程的_panic链表中,在没有defer需要处理时,会调用fatalpanic函数中止整个程序。
panci的参数信息及函数调用栈就是在fatalpanic函数中打印的。
在这里插入图片描述

defer函数处理

gopanic函数会逐个执行defer函数,然后逐个将_defer实例从链表中清除,如果是open-coded类型的defer,还需要额外处理。

嵌套defer

嵌套defer的处理其实并不复杂,当gopanic函数执行某个defer时,如果再次发生panic,
那么程序控制权会交给信的gopanic函数,信的gopanic函数会产生信的_panic实例,并把原_panic实例标记为aborted,然后继续处理剩余的defer函数。
前一个gopanic函数在执行defer时会标记开始状态d.started=true,并把当前_panic实例地址存放在_defer中。
信的gopanic函数再次遍历defer时就可以通过d.started标志判断是否存在被中断的panic,如果有,则把原_panic标记为aborted,然后继续处理剩余defer.

4. 总结

触发panic函数中的defer语句是否会执行? 会

func TestSeven(t *testing.T) {
	defer fmt.Println("A")
	defer fmt.Println("B")
	fmt.Println("C")
	panic("panic")
	defer fmt.Println("D")
}

在这里插入图片描述

上游函数中的defer语句是否会执行? 会

func TestSeven(t *testing.T) {
	defer fmt.Println("A")
	defer fmt.Println("B")
	fmt.Println("C")
	panic("panic")
	defer fmt.Println("D")
}

func TestEight(t *testing.T) {
	defer func() {
		recover()
	}()
	defer func() {
		fmt.Println("1")
	}()
	TestSeven(t)
}

在这里插入图片描述

其他协程中的defer语句是否会执行? 不会

func TestSeven(t *testing.T) {
	defer fmt.Println("A")
	defer fmt.Println("B")
	fmt.Println("C")
	panic("panic")
	defer fmt.Println("D")
}

func TestNine(t *testing.T) {
	defer func() {
		fmt.Println("Nine")
	}()
	go TestSeven(t)
	time.Sleep(time.Second)
}

在这里插入图片描述

defer语句中产生panic会发生什么? 中止当前defer后面的逻辑,接着处理其他的defer

func TestTen(t *testing.T) {
	defer func() {
		recover()
	}()
	defer fmt.Println("A")
	defer func() {
		fmt.Println("B")
		panic("inner")
		fmt.Println("C")
	}()
	panic("panic")
	defer fmt.Println("D")
}

在这里插入图片描述

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

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

相关文章

单片机基于TXW8301的Wi-Fi Halow物联网控制

目前市面上基于2.4/5.8G wifi通讯信号干扰很频繁,基于Wi-Fi Halow的sub1g wifi既可以绕过干扰还可以达到公里级别控制,并且实现高清图传非常方便。 什么是Wi-Fi Halow?有何优势? 早在2016年3月,Wi-Fi联盟就针对物联网…

YOLO v8目标检测(二)—v8理论与模型推理

YOLO v8目标检测 数据增强 使用场景:在数据加载器加载数据的过程中会使用到数据增强的相关方法,来构造数据集。模型推理方法进行学习之前首先复习了解图像数据增强的相关方法和步骤。 其中在v8的源代码中 augment.py 的源代码文件。它包含了几个用于数据…

【python】python基于 Q-learning 算法的迷宫游戏(源码+论文)【独一无二】

👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉公众号👈:测试开发自动化【获取源码商业合作】 👉荣__誉👈:阿里云博客专家博主、5…

java项目数据库 mysql 迁移到 达梦

目录 一、下载安装达梦数据库 1、下载 2、解压 3、安装 二、迁移 三、更改SpringBoot 的 yml文件 1、达梦创建用户 2、修改yml 一、下载安装达梦数据库 1、下载 下载地址 https://eco.dameng.com/download/ 点击下载 开发版 (X86平台) , 然后选择操作系统并点击立…

重装win10系统,“我们无法创建新的分区 也找不到现有的分区”“我们无法更新系统保留的分区”

重装系统,最开始用这篇教程保留数据的重装系统教程!(win10系统)_win10重装系统保留c盘数据-CSDN博客里win10官方的更新方法。想保留C盘以外的数据来重装系统 然后就会提示“我们无法更新系统保留的分区” 查到网上说这是因为MSR分…

python基础巩固

基本数据类型 可以用isinstance来判断 a111 isinstance(a,int) True数值运算: >>> 2 / 4 # 除法,得到一个浮点数 0.5 >>> 2 // 4 # 除法,得到一个整数 0 >>> 17 % 3 # 取余 2Python 字符串不能被改变。向一个…

支持向量机 及其分类案例详解(附Python 代码)

支持向量机分类器预测收入等级 我们将构建一个支持向量机(SVM)分类器,以预测一个人基于14个属性的收入等级。我们的目标是判断收入是否高于或低于每年$50,000。因此,这是一个二元分类问题。我们将使用在此处可用的人口普查收入数…

MySQL数据库(基础篇)

🌏个人博客主页:心.c 前言:今天讲解的是MySQL的详细知识点的,希望大家可以收货满满,话不多说,直接开始搞! 🔥🔥🔥文章专题:MySQL 😽感…

语言转文字

因为工作原因需要将语音转化为文字,经常搜索终于找到一个免费的好用工具,记录下使用方法 安装Whisper 搜索Colaboratory 右上方链接服务 执行 !pip install githttps://github.com/openai/whisper.git !sudo apt update && sudo apt install f…

Arduino IDE界面和设置(基础知识)

Arduino IDE界面和设置(基础知识) 1-2 Arduino IDE界面和设置如何来正确选择Arduino开发板型号如何正确选择Arduino这个端口如何来保存一个Arduino程序Arduino ide 的界面功能按钮验证编译上传新建打开保存工作状态 1-2 Arduino IDE界面和设置 大家好这…

软设之数据库概念结构设计

集成的方法: 多个局部E-R图一次集成 逐步集成,用累加的方式一次集成两个局部E-R 集成产生的冲突及解决办法: 属性冲突:包括属性冲突和属性取值冲突 命名冲突:包括同名异义和异名同义 结构冲突:包括同…

react.16+

1、函数式组件 在vite脚手架中执行&#xff1a; app.jsx: import { useState } from react import reactLogo from ./assets/react.svg import viteLogo from /vite.svg import ./App.cssfunction App() {console.log(this)return <h2>我是函数式组件</h2> }exp…

如何通过注解注入一个自定义的FactoryBean

一、引入依赖二、定义一个注解三、创建一个FactoryBean四、创建一个BeanPostProcessor4.1 其他关联类AnnotationUtilsServiceBeanNameBuilder 五、注入InstantiationAwareBeanPostProcessor到IoC中5.1 实现ImportBeanDefinitionRegistrar接口5.2 通过Import注入 六、使用6.1 打…

【proteus经典项目实战】51单片机用计数器中断实现100以内的按键计数并播放音乐

一、简介 一个基于8051微控制器的计数器系统&#xff0c;该系统能够通过按键输入递增计数&#xff0c;并且能够在达到100时归零。该系统将使用计数器中断和外部中断来实现其功能。 51单片机因其简单易用和成本效益高&#xff0c;成为电子爱好者和学生的首选平台。通过编程单片…

猫头虎分享 || 最全Python的Scapy库基础知识点汇总

&#x1f431;‍&#x1f464; 猫头虎分享 || Python的Scapy库基础知识点汇总 摘要 Scapy 是一个强大的Python库&#xff0c;用于网络数据包的生成、解析和操作。通过Scapy&#xff0c;开发者可以轻松地创建自定义数据包&#xff0c;捕获网络流量&#xff0c;并执行网络扫描。…

算法日记day 22

一、二叉搜索树中的插入操作 题目&#xff1a; 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入树中的值 value &#xff0c;将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 &#xff0c;新值和原始二叉搜索树中的任意节点值都不同。…

Python学习笔记46:游戏篇之外星人入侵(七)

前言 到目前为止&#xff0c;我们已经完成了游戏窗口的创建&#xff0c;飞船的加载&#xff0c;飞船的移动&#xff0c;发射子弹等功能。很高兴的说一声&#xff0c;基础的游戏功能已经完成一半了&#xff0c;再过几天我们就可以尝试驾驶 飞船击毁外星人了。当然&#xff0c;计…

【优秀python web系统毕设】基于python的全国招聘数据分析可视化系统,包括随机森林算法

1.1 研究背景 自1997年互联网开始在国内的招聘行业发展至今已有二十几年的历史&#xff0c;互联网招聘进入了蓬勃发展的“黄金时代”。根据智研咨询发布的《2023年中国互联网招聘行业发展现状》报告显示&#xff0c;截至2023年5月&#xff0c;中国互联网招聘平台中&#xff0c…

数据结构(Java):反射枚举Lambda表达式

目录 1、反射 1.1 反射的定义 1.2 反射机制的原理 1.3 反射相关类 1.4 Class类 1.4.1 相关方法 1.4.1.1 常用获得类相关的方法 ​编辑 1.4.1.2 常用获得类中属性相关的方法 1.4.1.3 获得类中构造器相关的方法 1.4.1.4 获得类中方法相关的方法 1.4.2 获取Class对象 1.…

DeFi革命:揭秘去中心化金融的核心技术与实操指南

目录 DeFi&#xff08;去中心化金融&#xff09;综述 基本特点 第一&#xff0c;DeFi 是无许可的金融 第二&#xff0c;DeFi 是无门槛的金融 第三&#xff0c;DeFi 是无人驾驶的金融 典型商业模式 闪电贷 MakerDAO 面临的挑战 DeFi技术要点 椭圆曲线签名 EIP-712:…