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

从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讲》

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CentOS系统环境搭建(十七)——elasticsearch设置密码

centos系统环境搭建专栏🔗点击跳转 elasticsearch设置密码 没有密码是很不安全的一件事😭 文章目录 elasticsearch设置密码1.设置密码2.登录elasticsearch3.登录kibana4.登录elasticsearch-head 1.设置密码 关于Elasticsearch的安装请看CentOS系统环境搭…

SpringBootWeb案例 Part 4

3. 修改员工 需求:修改员工信息 在进行修改员工信息的时候,我们首先先要根据员工的ID查询员工的信息用于页面回显展示,然后用户修改员工数据之后,点击保存按钮,就可以将修改的数据提交到服务端,保存到数据…

【校招VIP】产品思维分析之面试新的功能点设计

考点介绍: 这种题型是面试里出现频度最高,也是难度最大的一种,需要面试者对产品本身的功能、扩展性以及行业都有一定的了解。而且分析时间较短,需要一定的产品能力和回答技巧。 『产品思维分析之面试新的功能点设计』相关题目及解…

java+springboot+vue儿童慈善捐赠管理系统的设计与实现8n9e4

针对用户需求开发与设计,该技术尤其在各行业领域发挥了巨大的作用,有效地促进了“爱相连”儿童慈善管理的发展。然而,由于用户量和需求量的增加,信息过载等问题暴露出来,为改善传统线下管理中的不足,本文将…

Docker搭建LNMP----(超详细)

目录 ​编辑 一、项目环境 1.1 所有安装包下载: 1.3 服务器环境 1.4任务需求 二、Ngin 2.1、建立工作目录 2.2 编写 Dockerfile 脚本 2.3准备 nginx.conf 配置文件 2.4生成镜像 2.5创建自定义网络 2.6启动镜像容器 2.7验证 nginx、 三、Mysql 3.1建立…

传智教育广州校区又又又举行校内招聘会,多名学员被广东民生在线教育招入麾下

数字经济的高速发展以及经济形势的逐渐回暖,带动了企业对数字人才的用人需求增加,近日,传智教育旗下高端IT教育品牌黑马程序员多个校区接到了企业上门招聘的需求,各分校区通过举行校内招聘会,为用人企业和学员搭建了人…

一文速学-让神经网络不再神秘,一天速学神经网络基础-激活函数(二)

前言 思索了很久到底要不要出深度学习内容,毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新,很多坑都没有填满,而且现在深度学习的文章和学习课程都十分的多,我考虑了很久决定还是得出神经网络系列文章,…

实验二 tftp 服务器环境搭建

tftp 服务器环境搭建 tftp(Trivial File Transfer Protocol)即简单文件传输协议是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为69 【实验目的】 掌握 tftp 环境搭…

【目标检测】“复制-粘贴 copy-paste” 数据增强实现

文章目录 前言1. 效果展示代码说明3. 参考文档4. 不合适点 前言 本文来源论文《Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation》(CVPR2020),对其数据增强方式进行实现。 论文地址:https:/…

MediaPlayer音频与视频的播放介绍

作者:向阳逐梦 Android多媒体中的——MediaPlayer,我们可以通过这个API来播放音频和视频该类是Androd多媒体框架中的一个重要组件,通过该类,我们可以以最小的步骤来获取,解码和播放音视频。 它支持三种不同的媒体来源…

Talk | 上海交通大学官同坤:识别任意文本,隐式注意力与字符间蒸馏在文本识别中的应用

本期为TechBeat人工智能社区第525期线上Talk! 北京时间8月23日(周三)20:00,上海交通大学博士生—官同坤的Talk已准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “隐式注意力与字符间蒸馏在文本识别中的应用”,分享了识别…

使用docker-maven-plugin插件构建镜像并推送至私服Harbor

前言 如下所示,建议使用 Dockerfile Maven 插件,但该插件也停止维护更新了。因此先暂时使用docker-maven-plugin插件。 一、开启Docker服务器的远程访问 1.1 开启2375远程访问 默认的dokcer是不支持远程访问的,需要加点配置,开…

bh002- Blazor hybrid / Maui 保存设置快速教程

1. 建立工程 bh002_ORM 源码 2. 添加 nuget 包 <PackageReference Include"BootstrapBlazor.WebAPI" Version"7.*" /> <PackageReference Include"FreeSql" Version"*" /> <PackageReference Include"FreeSql.…

MyBatis分页插件PageHelper的使用及特殊字符的处理

目录 一、PageHelper简介 1.什么是分页 2.PageHelper是什么 3.使用PageHelper的优点 二、PageHelper插件的使用 原生limit查询 1. 导入pom依赖 2. Mybatis.cfg.xml 配置拦截器 3. 使用PageHelper进行分页 三、特殊字符的处理 1.SQL注入&#xff1a; 2.XML转义&#…

【Linux】【驱动】第一个相对完整的驱动编写

【Linux】【驱动】第一个相对完整的驱动编写 续1.驱动部分的代码2 app 代码3 操作相关的代码 续 这个章节会讲述去直接控制一个GPIO&#xff0c;高低电平。 因为linux不允许直接去操作寄存器&#xff0c;所以在操作寄存器的时候就需要使用到函数&#xff1a;ioremap 和iounma…

线性代数的学习和整理10:各种特殊类型的矩阵(草稿-----未完成 建设ing)

目录 1 图形化分类 1.1对称矩阵 1.2 梯形矩阵 1.3 三角矩阵 1.3.1 上三角矩阵 1.4 对角线矩阵 2 按各自功能分 2.1 等价矩阵 2.2 增广矩阵 2.3 伴随矩阵 2.4 正交矩阵 2.5 正交矩阵 2.6 相似矩阵 1 图形化分类 1.1对称矩阵 1.2 梯形矩阵 1.3 三角矩阵 1.3.1 上…

【ARM-Linux】项目,语音刷抖音项目

文章目录 所需器材装备操作SU-03T语音模块配置代码&#xff08;没有用wiring库&#xff0c;自己实现串口通信&#xff09;结束 所需器材 可以百度了解以下器材 orangepi-zero2全志开发板 su-03T语音识别模块 USB-TTL模块 一个安卓手机 一根可以传输的数据线 装备操作 安…

windows Etcd的安装与使用

一、简介 etcd是一个分布式一致性键值存储&#xff0c;其主要用于分布式系统的共享配置和服务发现。 etcd由Go语言编写 二、下载并安装 1.下载地址&#xff1a; https://github.com/coreos/etcd/releases 解压后的目录如下&#xff1a;其中etcd.exe是服务端&#xff0c;e…

C语言弯道超车必做好题锦集(编程题)

目录 前言&#xff1a; 1.计算日期到天数转换 2.尼科彻斯定理 3.密码检查 4.图片整理 5.寻找数组的中心下标 6.字符个数统计 7.多数元素 前言&#xff1a; 编程想要学的好&#xff0c;刷题少不了&#xff0c;我们不仅要多刷题&#xff0c;还要刷好题&#xff01;为此我…

C# .aspx网页获取RFID读卡器HTTP协议提交的访问文件Request获得卡号、机号,Response回应驱动读卡器显示响声

本示例使用的设备&#xff1a;RFID网络WIFI无线TCP/UDP/HTTP可编程二次开发读卡器POE供电语音-淘宝网 (taobao.com) 服务端代码&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.…