【golang】panic函数、recover函数以及defer语句

news2025/1/13 10:13:06

从panic被引发到程序终止运行的大致过程是什么?

大致过程:

某个函数中的某行代码有意无意地引发了一个panic。这时,初始的panic详情会被建立起来,并且该程序的控制权会立即从从行代码转移至调用其所属函数的那行代码上,也就是调用栈中的上一级。

意味着,此行代码所属函数的执行随即终止。紧接着,控制权并不会在此有片刻的停留,它又会立即转移至再上一级的调用代码处。控制权如此一级一级地沿着调用栈的反方向转播至顶端,也就是我们编写的最外层函数那里。

这里的最外层函数指的是go函数,对于主goroutine来说就是main函数。但是控制权也不会停留在那里,而是被Go语言运行时系统收回。

随后,程序崩溃并终止运行,承载程序这次运行的进程也会随之死亡而消失。与此同时,在这个控制权传播的过程中,-panic详情给会被逐渐地积累和完善,并会在程序终止之前被打印出来。

问题解析

panic可能是我们在无意间(或者说一不小心)引发的,如上文所述的索引越界。这类panic是真正的、在我们意料之外的程序异常。除此之外,我们还是可以有意地引发panic

Go语言的内建函数panic是专门用于引发panic的。panic函数使程序开发者可以在程序运行期间报告异常。

注意,这与从函数返回错误值的意义是完全不同的。当我们的函数返回一个非nil的错误值时,函数的调用方有权选择不处理,并且不处理的后果往往是不致命的。

这里的“不致命”的意思是,不至于使程序无法提供任何功能(也可以说僵死)或者直接崩溃并终止运行(也就是真死)。

但是,当一个 panic 发生时,如果我们不施加任何保护措施,那么导致的直接后果就是程序崩溃,就像前面描述的那样,这显然是致命的。

panic 详情会在控制权传播的过程中,被逐渐地积累和完善,并且,控制权会一级一级地沿着调用栈的反方向传播至顶端。在针对某个 goroutine 的代码执行信息中,调用栈底端的信息会先出现,然后是上一级调用的信息,以此类推,最后才是此调用栈顶端的信息。

eg:

main函数调用了caller1函数,而caller1函数又调用了caller2函数,那么caller2函数中代码的执行信息会先出现,然后是caller1函数中代码的执行信息,最后才是main函数的信息。

goroutine 1 [running]:
main.caller2()
 /Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:22 +0x91
main.caller1()
 /Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:15 +0x66
main.main()
 /Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:9 +0x66
exit status 2

image.png

怎样让panic包含一个值,以及应该让它包含什么样的值?

答案:其实很简单,在调用panic函数时,把某个值作为参数传给该函数就可以了。由于panic函数的唯一一个参数是空接口(也就是interface{})类型的,所以从语法上讲,它可以接受任何类型的值。

但是,我们最好传入error类型的错误值,或者其他的可以被有效序列化的值。这里的“有效序列化”指的是,可以更易读地去表示形式转换

一旦程序异常了,我们就一定要把异常的相关信息记录下来,这通常都是记到程序日志里。

我们在为程序排查错误的时候,首先要做的就是查看和解读程序日志;而最常用也是最方便的日志记录方式,就是记下相关值的字符串表示形式。

所以,如果你觉得某个值有可能会被记到日志里,那么就应该为它关联String方法。如果这个值是error类型的,那么让它的Error方法返回你为它定制的字符串表示形式就可以了。

怎样施加应对panic的保护措施,从而避免程序崩溃?

Go 语言的内建函数recover专用于恢复 panic,或者说平息运行时恐慌。recover函数无需任何参数,并且会返回一个空接口类型的值。

如果用法正确,这个值实际上就是即将恢复的 panic 包含的值。并且,如果这个 panic 是因我们调用panic函数而引发的,那么该值同时也会是我们此次调用panic函数时,传入的参数值副本。

请注意,这里强调用法的正确。我们先来看看什么是不正确的用法。

package main
import (
 "fmt"
 "errors"
)
func main() {
 fmt.Println("Enter function main.")
 // 引发 panic。
 panic(errors.New("something wrong"))
 p := recover()
 fmt.Printf("panic: %s\n", p)
 fmt.Println("Exit function main.")
}

在上面这个main函数中,我先通过调用panic函数引发了一个 panic,紧接着想通过调用recover函数恢复这个 panic。可结果呢?你一试便知,程序依然会崩溃,这个recover函数调用并不会起到任何作用,甚至都没有机会执行。

还记得吗?我提到过 panic 一旦发生,控制权就会迅速地沿着调用栈的反方向传播。所以,在panic函数调用之后的代码,根本就没有执行的机会。

那如果我把调用recover函数的代码提前呢? 也就是说,先调用recover函数,再调用panic函数会怎么样呢?

这显然也是不行的,因为,如果在我们调用recover函数时未发生 panic,那么该函数就不会做任何事情,并且只会返回一个nil。

那么,到底什么才是正确的recover函数用法呢?

defer语句就是被用来延迟执行代码的。延迟到什么时候呢? 这要延迟到该语句所在的函数即将执行结束的那一刻,无论结束执行的原因是什么。

一个defer语句总是由一个defer关键字和一个调用表达式组成。这里存在一些限制,有一些调用表达式是不能出现在这里的,包括:针对 Go 语言内建函数的调用表达式,以及针对unsafe包中的函数的调用表达式。

无论函数结束执行的原因是什么,其中的defer函数调用都会在它即将结束执行的那一刻执行。即使导致它执行结束的原因是一个 panic 也会是这样。正因为如此,我们需要联用defer语句和recover函数调用,才能够恢复一个已经发生的 panic

修正后的代码

package main
import (
 "fmt"
 "errors"
)
func main() {
     fmt.Println("Enter function main.")
 defer func(){
     fmt.Println("Enter defer function.")
 if p := recover(); p != nil {
     fmt.Printf("panic: %s\n", p)
 }
 fmt.Println("Exit defer function.")
 }()
     // 引发 panic。
     panic(errors.New("something wrong"))
     fmt.Println("Exit function main.")
}

这样,defer函数中的recover函数调用才会拦截,并恢复defer语句所属的函数,及其调用的代码中发生的所有 panic

如果一个函数中有多条defer语句,那么那几个defer函数调用的执行顺序是怎样的?

答案:在同一个函数中,defer函数调用的执行顺序与它们分别所属的defer语句的出现顺序(更严谨地说,是执行顺序)完全相反。

当一个函数即将结束执行时,其中的写在最下边的defer函数调用会最先执行,其次是写在它上边、与它的距离最近的那个defer函数调用,以此类推,最上边的defer函数调用会最后一个执行。

原理:

defer语句每次执行的时候,Go 语言会把它携带的defer函数及其参数值另行存储到一个队列中。

这个队列与该defer语句所属的函数是对应的,并且,它是先进后出(FILO)的,相当于一个栈。

在需要执行某个函数中的defer函数调用的时候,Go 语言会先拿到对应的队列,然后从该队列中一个一个地取出defer函数及其参数值,并逐个执行调用。

文章学习自郝林老师的《Go语言36讲》

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

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

相关文章

ICT产教融合创新实训基地物联网实训室建设方案

一、概述 1.1物联网定义 物联网工程(Internet of Things Engineering)是一种以信息技术(IT)来改善实体世界中人们生活方式的新兴学科,它利用互联网技术为我们的日常生活活动提供服务和增益,从而让各种智能…

1996-2022全球sar卫星数据

数据简介 1992年JAXA(Japan Aerospace Exploration Agency,日本宇宙航空研究开发机构)发射了一颗JERS-1卫星,该卫星携带有18*24m分辨率的SAR传感器。随后,JAXA又在2006年和2014年分别发射了带有SAR传感器的alos卫星和…

HelpLook 免费版与商业版的比较,帮助您快速选择!

HelpLook 是一款零代码、开箱即用的帮助中心及博客网站搭建工具,只需简单几步,即可帮助企业、机构、个人发布在线品牌内容站点。 HelpLook 提供多个版本方案供不同需求的用户选择,今天想着重跟大家分享免费版和商业版,将从三个方面…

DataFrame.plot函数详解(五)

DataFrame.plot函数详解(五) 散点图和箱体图实例 1. scatter DataFrame.plot.scatter(x, y, sNone, cNone, **kwargs) c: 是每个点的颜色,可以是一个值,也可以是数组值 s: 是每个点的大小,可以…

MCU和MPU你分得清楚吗?

最近有不少同学表示在学习嵌入式的过程中分不清MCU和MPU,这两个确实是长得很像、容易混淆的概念,这里我为大家仔细分辨一下。 从概念上讲,MCU指的是微控制器,优势在于“控制”,MPU指的是微处理器,优势在于“…

Redis笔记——(狂神说)待续

Nosql概述 为什么要用NoSql? 1、单机mysql的年代:90年代,网站访问量小,很多使用静态网页html写的,服务器没压力。 当时瓶颈是:1)数据量太大一个机器放不下。2)数据的索引(BTree),一个机器内存也…

激活函数总结

leakyRelu()激活函数 ReLU函数在输入大于0时返回输入值,否则返回0。 Leaky ReLU在输入小于0时会返回一个较小的负数,以保持一定的导数,使得信息可以继续向后传播。 Leaky ReLU函数: f(x) max(ax, x)其中,a是一个小…

基于ssm技术大健康综合咨询问诊平台源码和论文

基于ssm技术大健康综合咨询问诊平台源码和论文065 开发工具:idea 数据库mysql5.7 数据库链接工具:navcat,小海豚等 技术:ssm 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储&a…

API管理工具新趋势:一体化研发协作平台 Apipost

在数字化时代,API已经成为了应用程序之间进行通信的关键桥梁。随着API的普及和复杂性的增加,API研发和管理也面临着越来越多的挑战。为了更好地应对这些挑战,Apipost提供了一整套API研发工具,包括API设计、API调试、API文档和API自…

Kdab QML (part9)自由缩放时钟

文章目录 Kdab QML (part9)自由缩放时钟代码详细解释运行截图 Kdab QML (part9)自由缩放时钟 代码 import QtQuick 2.15 import QtQuick.Window 2.15Window {id: rootwidth: 500height: 500visible: truecolor: "lightgrey"title: qsTr("Hello World")It…

中央处理器(CPU):组成、指令周期、数据通路、控制方式、控制器、指令流水线,补充(多处理器系统、硬件多线程)

中央处理器(CPU,Central Processing Unit),计算机控制和运算的核心,是信息处理和程序运行的执行单元。 CPU主要功能:处理指令、执行操作、控制时间、处理中断、处理数据。 其中,处理指令、执行…

vue中利用Echarts实现飞线(飞机)地图样式

实现效果 思想:主要是三个要素:1 地图样式 2散点图 3飞线 组合配置后就形成以下效果。 第一步:vue中引入Echarts npm install vue-echarts echarts第二步:导入代码 代码已经写好,直接引入运行就好了,关键…

NO.02 依赖注入

目录 1、前言 2、两种依赖注入的方式 2.1 依赖注入之setter注入 2.2 依赖注入之构造器注入 3、依赖注入之特殊值处理 3.1 字面量赋值 3.2 null值赋值 3.3 XML实体&#xff08;<&#xff09; 4、完整测试类 1、前言 依赖注入就是为类的属性赋值&#xff0c;在我们获…

卷积神经网络实现猴痘疾病图像分类 - P4

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;Pytorch实战 | 第P4周&#xff1a;猴痘病识别&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff1a;K同学的学习圈子 目录…

Python数据分析实战-对列表里面的元素绘制词云图(附源码和实现效果)

实现功能 词云也叫文字云&#xff0c;是一种可视化的结果呈现&#xff0c;原理就是统计文本中高频出现的词&#xff0c;将结果生成一张图片&#xff0c;直观的获取数据的重点信息 实现代码 from wordcloud import WordCloud import matplotlib.pyplot as plt# 假设你的字符串…

浅谈大数据智能审计如何助力审计工作

随着互联网大数据的持续发展&#xff0c;大数据审计近年来面对着相等的机遇和挑战。那么&#xff0c;如果利用大数据等相关技术对审计工作作出突出贡献&#xff0c;单位和企业又该从何入手做好大数据审计工作应用&#xff0c;这些都成为每位审计人员将要面临的重要问题。 1. 政…

电机控制软件框架

应用层包括main 主函数模块&#xff0c;ISR 中断处理函数模块、时基Systick 模块和BLDC 应用接口模块&#xff1b;算法层包括BLDC Algorithm 模块和PID control 模块&#xff1b;驱动层&#xff08;Driver layer&#xff09;&#xff1a;包括GD32Fxx_Standard_peripheral libra…

基于SSM的OA办公系统Java企业人事信息管理jsp源代码MySQL

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 基于SSM的OA办公系统 系统有1权限&#xff1a;管理员…

课程项目设计--spring security--认证管理功能--宿舍管理系统--springboot后端

写在前面&#xff1a; 还要实习&#xff0c;每次时间好少呀&#xff0c;进度会比较慢一点 本文主要实现是用户管理相关功能。 前文项目建立 文章目录 验证码功能验证码配置验证码生成工具类添加依赖功能测试编写controller接口启动项目 security配置拦截器配置验证码拦截器 …

台积电美国厂施工现场混乱,真令人头痛 | 百能云芯

近日&#xff0c;英伟达公司的财报表现异常亮眼&#xff0c;摩根士丹利不仅点名了台积电成为最大的受益者&#xff0c;还预测每售出一颗H100英伟达芯片&#xff0c;台积电就能获得900美元的利润。然而&#xff0c;美国媒体却曝出了一则不利的消息&#xff0c;称美国亚利桑那州的…