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…...
MedGemma 1.5开源医疗模型:本地化部署满足等保2.0三级与GDPR双合规要求
MedGemma 1.5开源医疗模型:本地化部署满足等保2.0三级与GDPR双合规要求 1. 项目概述与核心价值 MedGemma 1.5是基于Google Gemma架构开发的医疗专用AI模型,专门针对医学问答、病理分析和术语解释场景优化。这个4B参数规模的模型经过PubMed、MedQA等专业…...
Phi-4-reasoning-vision-15B企业案例:银行客户经理用截图快速生成信贷摘要
Phi-4-reasoning-vision-15B企业案例:银行客户经理用截图快速生成信贷摘要 1. 业务痛点与解决方案 1.1 银行信贷业务的效率瓶颈 在传统银行信贷审批流程中,客户经理需要花费大量时间整理客户资料、录入系统信息、撰写信贷报告。一个典型的信贷审批案例…...
OBS多平台直播插件:3步搞定全网同步推流,让内容覆盖提升300%
OBS多平台直播插件:3步搞定全网同步推流,让内容覆盖提升300% 【免费下载链接】obs-multi-rtmp OBS複数サイト同時配信プラグイン 项目地址: https://gitcode.com/gh_mirrors/ob/obs-multi-rtmp 还在为每次直播只能选择一个平台而烦恼吗࿱…...
告别代码噩梦:用Awesome-Dify-Workflow零代码30分钟实现企业级登录系统
告别代码噩梦:用Awesome-Dify-Workflow零代码30分钟实现企业级登录系统 【免费下载链接】Awesome-Dify-Workflow 分享一些好用的 Dify DSL 工作流程,自用、学习两相宜。 Sharing some Dify workflows. 项目地址: https://gitcode.com/GitHub_Trending/…...
MacBook Intel芯片用户看过来:保姆级Anaconda安装与国内镜像源配置全攻略
MacBook Intel芯片用户看过来:保姆级Anaconda安装与国内镜像源配置全攻略 作为一名长期使用MacBook进行Python开发的工程师,我深知环境配置对于初学者来说可能是个不小的挑战。特别是对于使用Intel芯片的MacBook用户,虽然相比M1芯片少了些兼容…...
MambaAD实战:5分钟搞定工业缺陷检测的SoTA模型部署(附代码)
MambaAD工业缺陷检测实战:从模型原理到产线部署全指南 引言:当状态空间模型遇见工业质检 在液晶面板生产线上,一个0.1mm的亮点缺陷可能导致整批产品报废;在汽车零部件铸造车间,细微的表面裂纹可能引发严重的安全隐患。…...
OpenClaw跨平台部署:nanobot镜像在mac/Windows双系统实测
OpenClaw跨平台部署:nanobot镜像在mac/Windows双系统实测 1. 为什么选择nanobot镜像 第一次听说nanobot这个轻量级OpenClaw镜像时,我正被本地部署大模型的资源消耗问题困扰。作为一个经常在macOS和Windows双系统切换的开发者,我需要一个能在…...
医药行业用友 YonSuite 一体化管理方案
医保新规 4 月 1 日落地|医药企业破局:数智化 合规 精细化,活下去且活得好2026 年 4 月 1 日,医保新规全面执行,集采深化、价格严控、全链路监管,医药行业正式告别高毛利、粗放式、渠道为王的旧时代&…...
嵌入式开源软件应用的五项关键实践
嵌入式开源软件应用的五项关键实践1. 开源软件在嵌入式系统中的价值与挑战开源软件已成为现代嵌入式系统开发的重要组成部分。通过合理利用开源组件,开发团队可以显著缩短开发周期,降低研发成本,同时获得经过社区验证的可靠解决方案。然而&am…...
气候降尺度全流程实战:从 CMIP6 数据到极端气候预估,科研人一站式通关
做水文气象、气候学、地理遥感、生态环境等领域的科研人,是不是都逃不过这些噩梦:尺度鸿沟难跨越:GCM 粗网格(>100km)和流域 / 城市精细尺度(<10km)不匹配,动力降尺度成本太高…...
