Go中Panic and Recover
什么是Panic?
在 Go 程序中处理异常情况的惯用方法是使用errors.。errors足以应对程序中出现的大多数异常情况。
**但有些情况下,程序在出现异常情况后无法继续执行。在这种情况下,我们使用panic提前终止程序。当函数遇到恐慌时,其执行将停止,所有延迟的函数都会被执行,然后控制权返回给其调用者。这个过程一直持续到当前goroutine的所有函数都返回为止,此时程序将打印恐慌消息,然后打印堆栈跟踪,然后终止。**当我们编写示例程序时,这个概念会更加清晰。
**可以重新获得对发生恐慌的程序的控制,recover**我们将在本教程的后面讨论这一点。
可以认为panic和recover类似于Java等其他语言中的try-catch-finally习惯用法,只不过它们在Go中很少使用。
什么时候应该使用恐慌?
一个重要因素是您应该避免恐慌并尽可能恢复和使用错误。只有在程序无法继续执行的情况下才应该使用恐慌和恢复机制。
恐慌有两个有效的用例。
- 程序无法继续执行的不可恢复错误。 一个示例是 Web 服务器无法绑定到所需端口。在这种情况下,恐慌是合理的,因为如果端口绑定本身失败,就无事可做。
- 程序员错误。 假设我们有一个接受指针作为参数的方法,并且有人使用参数调用该方法
nil。nil在这种情况下,我们可能会感到恐慌,因为调用带有参数且期望有效指针的方法是程序员的错误。
恐慌的例子
panic下面提供了内置函数的签名,
func panic(interface{})
当程序终止时,传递给恐慌函数的参数将被打印。当我们编写示例程序时,它的用途就会很清楚。所以让我们立即这样做。
我们将从一个人为的例子开始,展示恐慌是如何运作的。
package mainimport ("fmt"
)func fullName(firstName *string, lastName *string) {if firstName == nil {panic("runtime error: first name cannot be nil")}if lastName == nil {panic("runtime error: last name cannot be nil")}fmt.Printf("%s %s\n", *firstName, *lastName)fmt.Println("returned normally from fullName")
}func main() {firstName := "Elon"fullName(&firstName, nil)fmt.Println("returned normally from main")
}
Run in playground
上面是一个打印一个人的全名的简单程序。第 7 行中的函数fullName。打印一个人的全名。该函数检查firstName和lastName指针是否nil。如果是nil函数调用则panic带有相应的消息。当程序终止时将打印此消息。
运行该程序将打印以下输出,
panic: runtime error: last name cannot be nilgoroutine 1 [running]:
main.fullName(0x0?, 0xc00003e730?)/tmp/sandbox3307338859/prog.go:12 +0x106
main.main()/tmp/sandbox3307338859/prog.go:20 +0x2f
让我们分析这个输出,以了解恐慌是如何工作的以及程序恐慌时如何打印堆栈跟踪。
在19 行号中。我们分配Elon给firstName. 我们在第 20行中使用调用fullName函数。 因此第 11 行条件的会满足,程序会恐慌。当遇到恐慌时,程序执行终止,传递给恐慌函数的参数将被打印,然后是堆栈跟踪。由于程序在第12 行的紧急函数调用之后终止后面的不会被执行。
该程序首先打印传递给函数的消息panic,
panic: runtime error: last name cannot be nil
然后打印堆栈跟踪。
该程序在第12 行出现恐慌。因此,
goroutine 1 [running]:
main.fullName(0xc00006af58, 0x0)/tmp/sandbox210590465/prog.go:12 +0x193
将首先打印。然后将打印堆栈中的下一项。在我们的例子中,在20行fullName调用的地方是堆栈跟踪中的下一项。因此接下来会打印它。
main.main()/tmp/sandbox210590465/prog.go:20 +0x4d
现在我们已经到达导致恐慌的顶级函数,并且上面没有更多级别,因此没有更多内容可打印。
再举一个例子
恐慌也可能是由运行时发生的错误引起的,例如尝试访问切片中不存在的索引。
让我们编写一个人为的示例,该示例会因越界切片访问而产生恐慌。
package mainimport ("fmt"
)func slicePanic() {n := []int{5, 7, 4}fmt.Println(n[4])fmt.Println("normally returned from a")
}
func main() {slicePanic()fmt.Println("normally returned from main")
}
Run in playground
在上面的程序中, 我们正在尝试访问切片n[4]中的无效索引。该程序将出现以下输出,
panic: runtime error: index out of range [4] with length 3goroutine 1 [running]:
main.slicePanic()/tmp/sandbox942516049/prog.go:9 +0x1d
main.main()/tmp/sandbox942516049/prog.go:13 +0x22
恐慌期间推迟通话
让我们回忆一下恐慌的作用。当函数遇到恐慌时,其执行将停止,所有延迟的函数都会被执行,然后控制权返回给其调用者。这个过程一直持续到当前 goroutine 的所有函数都返回为止,此时程序将打印恐慌消息,然后打印堆栈跟踪,然后终止。
在上面的示例中,我们没有推迟任何函数调用。如果存在延迟函数调用,则会执行该函数,然后将控制权返回给其调用者。
让我们稍微修改一下上面的示例并使用 defer 语句。
package mainimport ("fmt"
)func fullName(firstName *string, lastName *string) {defer fmt.Println("deferred call in fullName")if firstName == nil {panic("runtime error: first name cannot be nil")}if lastName == nil {panic("runtime error: last name cannot be nil")}fmt.Printf("%s %s\n", *firstName, *lastName)fmt.Println("returned normally from fullName")
}func main() {defer fmt.Println("deferred call in main")firstName := "Elon"fullName(&firstName, nil)fmt.Println("returned normally from main")
}
Run in playground
所做的唯一更改是在第 8 和 20行中添加了延迟函数调用。
该程序打印,
deferred call in fullName
deferred call in main
panic: runtime error: last name cannot be nilgoroutine 1 [running]:
main.fullName(0xc00006af28, 0x0)/tmp/sandbox451943841/prog.go:13 +0x23f
main.main()/tmp/sandbox451943841/prog.go:22 +0xc6
当程序在第13行发生恐慌时。首先执行任何延迟函数调用,然后控制权返回到执行延迟调用的调用者,依此类推,直到到达顶层调用者。
在我们的例子中,defer在第 8行的声明。首先执行fullName函数。这将打印以下消息。
deferred call in fullName
然后控制返回到main执行延迟调用的函数,因此打印:
deferred call in main
现在控件已到达顶层函数,因此程序会打印紧急消息,然后打印堆栈跟踪,然后终止。
从恐慌中恢复
恢复是一个内置函数,用于重新获得对发生恐慌的程序的控制。
下面提供了恢复函数的签名,
func recover() interface{}
仅当在延迟函数内部调用时恢复才有用。在延迟函数内执行恢复调用可以通过恢复正常执行来停止恐慌序列,并检索传递给恐慌函数调用的错误消息。如果在延迟函数之外调用恢复,它不会停止恐慌序列。
让我们修改我们的程序并使用recover在panic之后恢复正常执行。
package mainimport ("fmt"
)func recoverFullName() {if r := recover(); r != nil {fmt.Println("recovered from ", r)}
}func fullName(firstName *string, lastName *string) {defer recoverFullName()if firstName == nil {panic("runtime error: first name cannot be nil")}if lastName == nil {panic("runtime error: last name cannot be nil")}fmt.Printf("%s %s\n", *firstName, *lastName)fmt.Println("returned normally from fullName")
}func main() {defer fmt.Println("deferred call in main")firstName := "Elon"fullName(&firstName, nil)fmt.Println("returned normally from main")
}
Run in playground
当发生恐慌时,将调用fullName延迟函数来停止恐慌
该程序将打印,
recovered from runtime error: last name cannot be nil
returned normally from main
deferred call in main
当程序在第 19行发生恐慌时,延迟recoverFullName函数被调用,该函数又调用recover()以重新获得对恐慌序列的控制。呼叫行recover() 返回传递给的参数panic()并因此打印,
recovered from runtime error: last name cannot be nil
执行后recover(),恐慌停止,控制权返回给调用者,在本例中为main函数。main由于恐慌已恢复,程序从第 29 行开始继续正常执行。它打印returned normally from main 后跟deferred call in main
让我们再看一个示例,其中我们从由于访问切片的无效索引而引起的恐慌中恢复。
package mainimport ("fmt"
)func recoverInvalidAccess() {if r := recover(); r != nil {fmt.Println("Recovered", r)}
}func invalidSliceAccess() {defer recoverInvalidAccess()n := []int{5, 7, 4}fmt.Println(n[4])fmt.Println("normally returned from a")
}func main() {invalidSliceAccess()fmt.Println("normally returned from main")
}
Run in playground
运行上面的程序将输出,
Recovered runtime error: index out of range [4] with length 3
normally returned from main
从输出中,您可以了解到我们已经从恐慌中恢复过来。
恢复后获取堆栈跟踪
如果我们从恐慌中恢复,我们就会丢失有关恐慌的堆栈跟踪。即使在恢复后的上面的程序中,我们也丢失了堆栈跟踪。
有一种方法可以使用Debug包的PrintStack函数打印堆栈跟踪
package mainimport ("fmt""runtime/debug"
)func recoverFullName() {if r := recover(); r != nil {fmt.Println("recovered from ", r)debug.PrintStack()}
}func fullName(firstName *string, lastName *string) {defer recoverFullName()if firstName == nil {panic("runtime error: first name cannot be nil")}if lastName == nil {panic("runtime error: last name cannot be nil")}fmt.Printf("%s %s\n", *firstName, *lastName)fmt.Println("returned normally from fullName")
}func main() {defer fmt.Println("deferred call in main")firstName := "Elon"fullName(&firstName, nil)fmt.Println("returned normally from main")
}
Run in playground
在上面的程序中,我们使用debug.PrintStack()在第 11 行来打印堆栈跟踪。
该程序将打印,
recovered from runtime error: last name cannot be nil
goroutine 1 [running]:
runtime/debug.Stack(0x37, 0x0, 0x0)/usr/local/go-faketime/src/runtime/debug/stack.go:24 +0x9d
runtime/debug.PrintStack()/usr/local/go-faketime/src/runtime/debug/stack.go:16 +0x22
main.recoverFullName()/tmp/sandbox771195810/prog.go:11 +0xb4
panic(0x4a1b60, 0x4dc300)/usr/local/go-faketime/src/runtime/panic.go:969 +0x166
main.fullName(0xc0000a2f28, 0x0)/tmp/sandbox771195810/prog.go:21 +0x1cb
main.main()/tmp/sandbox771195810/prog.go:30 +0xc6
returned normally from main
deferred call in main
从输出中,您可以了解到恐慌已恢复并被recovered from runtime error: last name cannot be nil打印。接下来,打印堆栈跟踪。然后恐慌恢复后打印
returned normally from main
deferred call in main
恐慌、恢复和 Goroutine
仅当从同一个发生恐慌的goroutine调用时,Recover 才起作用。**不可能从不同 goroutine 中发生的恐慌中恢复。**让我们通过一个例子来理解这一点。
package mainimport ("fmt"
)func recovery() {if r := recover(); r != nil {fmt.Println("recovered:", r)}
}func sum(a int, b int) {defer recovery()fmt.Printf("%d + %d = %d\n", a, b, a+b)done := make(chan bool)go divide(a, b, done)<-done
}func divide(a int, b int, done chan bool) {fmt.Printf("%d / %d = %d", a, b, a/b)done <- true}func main() {sum(5, 0)fmt.Println("normally returned from main")
}
Run in playground
在上面的程序中,该函数divide()将在第 22 行发生恐慌。因为 b 为零并且不可能将数字除以零。该sum()函数调用一个延迟函数recovery(),用于从恐慌中恢复。该函数divide()在第 1 7行作为单独的 goroutine 被调用。 我们在18行号的done通道上等待。确保divide()完成执行。
你认为该程序的输出是什么?恐慌情绪会恢复吗?答案是不。恐慌将无法恢复。这是因为该recovery函数存在于不同的 goroutine 中,并且恐慌发生在divide()不同 goroutine 中的函数中。因此不可能恢复。
运行该程序将打印,
5 + 0 = 5
panic: runtime error: integer divide by zerogoroutine 18 [running]:
main.divide(0x5, 0x0, 0xc0000a2000)/tmp/sandbox877118715/prog.go:22 +0x167
created by main.sum/tmp/sandbox877118715/prog.go:17 +0x1a9
您可以从输出中看到恢复尚未发生。
如果该divide()函数在同一个 goroutine 中调用,我们就会从恐慌中恢复过来。
如果17 行号程序修改为
go divide(a, b, done)
到
divide(a, b, done)
由于恐慌发生在同一个 goroutine 中,因此恢复将会发生。如果程序在进行上述更改后运行,它将打印
5 + 0 = 5
recovered: runtime error: integer divide by zero
normally returned from main
相关文章:
Go中Panic and Recover
什么是Panic? 在 Go 程序中处理异常情况的惯用方法是使用errors.。errors足以应对程序中出现的大多数异常情况。 **但有些情况下,程序在出现异常情况后无法继续执行。在这种情况下,我们使用panic提前终止程序。当函数遇到恐慌时,…...
webpack 与 grunt、gulp 的不同?
结论先行: Webpack、Grunt 和 Gulp 都是前端开发中常用的构建工具,但是 Webpack 是基于模块化打包的工具,并支持模块化开发。而 Grunt 和 Gulp 都是基于任务的构建工具,自动执行指定的任务,但不支持模块化开发。 1、相…...
园区网真实详细配置大全案例
实现要求: 1、只允许行政部电脑对全网telnet管理 2、所有dhcp都在核心 3、wifi用户只能上外网,不能访问局域网其它电脑 4、所有接入交换机上bpdu保护 5、只允许vlan 10-40上网 5、所有接入交换机开dhcp snoop 6、所有的交换机指定核心交换机为ntp时间服务…...
小程序video标签在底部出现1px无法去除的黑色线
问题描述 参见社区问题详情 此问题只会在ios手机真机中出现,视频底部出现1px无法去除的黑色线 解决方法 1.尝试过video各种配置,以为是设置参数导致 2.尝试过父元素设置height:200px;overflow:hidden;vi…...
渗透工具使用及思路总结(持续更新)
扫描类 nmap 快速扫描开放端口 nmap --min-rate 10000 -p- 10.129.252.63扫描详细全服务 nmap -sV -A -p 22,80 10.129.252.63 nmap -sV -A -p- 10.129.252.63-l:显示正在监听的 TCP 和 UDP 端口; -a:显示所有活动的 TCP 连接; -A <网络类型>或 - <网络类型&g…...
速卖通新卖家有必要测评吗?
大家都知道通过测评可以提升产品的转化率,提升产品的销量,那么做速卖通的卖家有必要测评吗? 测评就是类似于国内电商的补单,而一个类目里面竞争很大很卷的话,不去补销量来提升产品的权重,凭借着平台给的自…...
从lc114. 二叉树展开为链表到lc-LCR 155二叉搜索树转化为排序的双向链表
1 lc114. 二叉树展开为链表 1.1 描述 进阶:你可以使用原地算法(O(1) 额外空间)展开这棵树吗? 1.2 解法一: 先序遍历这棵树并且将节点加入到一个list中,随后按顺序将list中的每一个元素的left指针置换为…...
做读书笔记时的一个高效小技巧
你好,我是 EarlGrey,一名双语学习者,会一点编程,目前已翻译出版《Python 无师自通》、《Python 并行编程手册》等书籍。 在这里,我会持续和大家分享好书、好工具和高效生活、工作技巧,欢迎大家一起提升认知…...
Redis7.x 高级篇
Redis7.x 高级篇 Redis版本发行时间Redis单线程说的是什么东西 Redis版本发行时间 Redis单线程说的是什么东西...
2023辽宁省数学建模B题数据驱动的水下导航适配区分类预测完整原创论文分享(python求解)
大家好呀,从发布赛题一直到现在,总算完成了辽宁省数学建模B题完整的成品论文。 本论文可以保证原创,保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 B用Python+SPSSPRO求解&…...
向量数据库的崛起与多元化场景创新
向量数据库的崛起与多元化场景创新 前言: 在当今数字化时代,数据被认为是黄金,对于企业、科学家和决策者而言都具有巨大的价值。然而,随着数据规模的不断增长,有效地管理、存储和检索数据变得愈发复杂。这就引入了向量…...
面试10000次依然会问的【ReentrantLock】,你还不会?
引言 在并发编程的世界中,ReentrantLock扮演着至关重要的角色。它是一个实现了重入特性的互斥锁,提供了比synchronized关键字更加灵活的锁定机制。ReentrantLock属于java.util.concurrent.locks包,是Java并发API的一部分。 与传统的synchro…...
Bat批量处理
一:创建文件夹 excel创建文件 复制出来新建文本文件 另存为bat 双击bat 二:批量移动文件 A列:获取的文件名列表 dir /b/o:n> original.txt B列:填充序号 C列公式:每隔9行增加1 INT((ROW(B1)-1)/9)1 D列公式&am…...
【一、http】go的http基本请求方法
1、http的基本请求 package mainimport ("bytes""fmt""io""net/http""net/url" )func post(){r, err : http.Post("http://httpbin.org/post", "", nil)if err ! nil {fmt.Println("ss")}de…...
【软考中级】软件设计师-下午题
下午题 试题一 黑洞:加工有输入无输出 白洞(奇迹):加工有输出无输入 灰洞:数据流输入的加工不足以产生输出 结构化语言: IF *** THEN ELSE IF *** THEN ******* END IF END IF 数据流的父子图平衡,如果父子图平衡就不…...
(03)Mycat实现读写分离
1、schema.xml <?xml version"1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat"http://io.mycat/"><schema name"TESTDB" checkSQLschema"false" sqlMaxLimit"…...
[SSD综述1.7] SSD接口形态: SATA、M.2、U.2、PCIe、BGA
依公知及经验整理,原创保护,禁止转载。 专栏 《SSD入门到精通系列》 <<<< 返回总目录 <<<< 前言 犹记得当年Windows 7系统体验指数中,那5.9分磁盘分数,在其余四项的7.9分面前,似乎已经告诉我们机械硬盘注定被时代淘汰。势如破竹的SSD固态硬盘,彻…...
20.5 OpenSSL 套接字RSA加密传输
RSA算法同样可以用于加密传输,但此类加密算法虽然非常安全,但通常不会用于大量的数据传输,这是因为RSA算法加解密过程涉及大量的数学运算,尤其是模幂运算(即计算大数的幂模运算),这些运算对于计…...
C#中的19个LINQ to XML 类
System.Xml.Linq 命名空间包含 LINQ to XML 的19个类。 LINQ to XML 是内存中的 XML 编程接口,使能轻松有效地修改 XML 文档。 微软在 LINQ 上投入了很大的精力,使我们在编程时感觉到很舒服。处理 XML 时使用最多的三个类:XElement、XAttribu…...
取消elementUI中table的选中状态和勾选状态赋值
一、取消所有选中 1、表格上绑定ref 2、清空用户选中数据 this.$refs.loopRef.clearSelection()二、勾选状态赋值 获取数据,flag为true则是选中状态,并将前面勾选框设为选中状态 this.listData.forEach(item> {if(row.flag1){this.$refs.loopRef.to…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
