Go 语言核心 36 讲
郝林
《Go 并发编程实战》作者,前轻松筹大数据负责人
79610 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 55 讲
Go 语言核心 36 讲
15
15
1.0x
00:00/00:00
登录|注册

21 | panic函数、recover函数以及defer语句 (上)

延迟执行
恢复程序控制权
引发panic
运行时恐慌
错误处理策略
错误信息
错误传播
错误检查
自定义错误类型
内建错误类型
panic转化为error类型值
defer语句
recover函数
panic函数
错误设计方式
错误值处理技巧
错误类型
panic函数、recover函数以及defer语句
Go语言错误处理

该思维导图由 AI 生成,仅供参考

我在上两篇文章中,详细地讲述了 Go 语言中的错误处理,并从两个视角为你总结了错误类型、错误值的处理技巧和设计方式。
在本篇,我要给你展示 Go 语言的另外一种错误处理方式。不过,严格来说,它处理的不是错误,而是异常,并且是一种在我们意料之外的程序异常。

前导知识:运行时恐慌 panic

这种程序异常被叫做 panic,我把它翻译为运行时恐慌。其中的“恐慌”二字是由 panic 直译过来的,而之所以前面又加上了“运行时”三个字,是因为这种异常只会在程序运行的时候被抛出来。
我们举个具体的例子来看看。
比如说,一个 Go 程序里有一个切片,它的长度是 5,也就是说该切片中的元素值的索引分别为01234,但是,我在程序里却想通过索引5访问其中的元素值,显而易见,这样的访问是不正确的。
Go 程序,确切地说是程序内嵌的 Go 语言运行时系统,会在执行到这行代码的时候抛出一个“index out of range”的 panic,用以提示你索引越界了。
当然了,这不仅仅是个提示。当 panic 被抛出之后,如果我们没有在程序里添加任何保护措施的话,程序(或者说代表它的那个进程)就会在打印出 panic 的详细情况(以下简称 panic 详情)之后,终止运行。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go语言中的panic函数、recover函数以及defer语句是处理程序异常的重要方式。本文介绍了panic函数引发的运行时恐慌,以及程序在出现panic时的终止过程。文章指出,panic是一种只能在程序运行期间抛出的程序异常,可能是无意间引发的,也可以有意地使用panic函数引发。然而,如果不加以处理,panic会导致程序崩溃并终止运行。读者可以通过深入了解panic被引发后的程序终止过程和正确解读panic详情来提升调试和错误排查的能力。此外,文章还提出了一个思考题,探讨了如何将panic转化为error类型值并将其作为函数的结果值返回给调用方。通过本文的介绍,读者可以快速了解Go语言中处理异常的方式,以及如何正确处理panic,recover和defer。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 语言核心 36 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(16)

  • 最新
  • 精选
  • 老师,你好,我有一个疑问,请教一下,谢谢~ Go在设计的时候没有设计try...catch...finally这样的方式来捕获异常。 我在网上查很多人用panic、defer和recover组合来实现异常的捕获,甚至很多都将这个二次封装之后作为一个库来进行使用。 我的疑问是,从Go的设计角度为什么要这么做?是出于什么样的目的,还是他俩之间有什么优劣? 非常感谢~,烦请解答。

    作者回复: Go的错误处理机制是由两个部分组成的,panic代表着特殊的(或者说意外的)错误,error代表着普通的错误。与try-catch不同,error并不是打断正常的控制流程的执行。单单这一点来讲,就已经是非常好的进步了。相比之下,panic会打断正常的控制流程。从这一点上看,panic与try-catch很像。 说到这里,你可能也意识到了,try-catch是一套行为单一的错误处理机制,而Go语言的(error+panic)把错误处理机制在代码级别分为了两个部分。 这样的好处是,倒逼开发者去思考,什么时候应该返回普通的错误,什么时候应该抛出意外的错误。这种思考在设计一个程序的错误体系的时候是非常重要的,关系到程序运行的稳定性。 至于缺点,error容易被滥用,导致程序中到处是 if err != nil 的代码。但是我们要清楚的是,这往往是程序设计上的问题,而不是语言层面的问题。如果不当心,try-catch照样会被弄的满屏都是。而且try-catch还有一个颗粒度和数量的问题(与临界区的颗粒度和数量问题类似)。 总之,我个人认为Go语言的错误处理机制是一种创新和进步。不过,由于容易被滥用,Go语言团队不是还在近几年一直在考虑更好的解决方案吗。我也很期待他们新的设计。

    2020-03-06
    8
    20
  • 唐丹
    郝大,你好,我在golang 8中通过recover处理panic时发现,必须在引发panic的当前协程就处理掉,否则待其传递到父协程直至main方法中,都不能通过recover成功处理掉了,程序会因此结束。请问这样设计的原因是什么?那么协程是通过panic中记录的协程id来区分是不是在当前协程引发的panic的吗?另外,这样的话,我们应用程序中每一个通过go新起的协程都应该在开始的地方recover,否则即使父协程有recover也不能阻止程序因为一个意外的panic而挂掉?盼望解答,谢谢🙏

    作者回复: 只要在调用栈路径上就都可以处理,如果你用了defer语句和recover函数等正确处理方式还是不行的话,就要看看这个panic是不是不了恢复的。一些runtime抛出来的panic是不可恢复的,因为问题很严重必须整改代码才行。

    2018-09-28
    5
    12
  • 沐夜星光
    “控制权如此一级一级地沿着调用栈的反方向传播至顶端,也就是我们编写的最外层函数那里”。最外层函数是go函数,也就说当panic触发,通过其所在的goroutine,将控制权转移给运行时系统时,是不一定经过main函数的吗?另外老师能不能讲讲,go是怎么回收一个进程的,怎么处理运行中的goroutine以及涉及的资源。

    作者回复: 这会经过main函数,异步调用也是调用。main函数就是主goroutine的go函数。 go回收进程?这没什么稀奇的啊,就是调用操作系统的底层API,你可以查看 runtime.main 函数了解相关过程,或者查看 runtime.exit 函数了解进程退出时调用的API。 在一个goroutine中的go函数执行完之后,这个goroutine会转为_Gidle状态,其中的所有关键字段都会被重置,它的栈内存也会被释放。最后,它会被放入自由G列表。你可以通过查看 runtime.goexit0 了解到。

    2020-05-21
    2
    4
  • 我好像一点都不像程序员
    初学go都会吐槽说没有 try catch , 应该不止我一个

    作者回复: 每种语言的风格都不同啊,习惯习惯就好了(注意力可以更多的放在“怎样高效构建优秀软件上” ,语法的一些特点不属于关键问题 :-) ),而且后续Go在这方面会有改善。

    2021-03-25
    4
    2
  • 阿俊
    请问老师 曾经我也是JAVA程序员 以前做JAVA项目 那时候遇到 例如 编写业务代码 “用户名称”不符合某种条件 业务逻辑代码会写throws 包含错误信息的自定义业务Exception 然后最终做一个try catch 然后catch到业务excption提取其中的message反馈给最终结果;如果在go项目开发中遇到类似问题,是不是可以通过的手动触发panic, 再在最终出口的地方,通过defer结合recover全局捕获相关panic并提取信息反馈给客户端也防止因此整个application崩掉,这种方式是否可取? 想听听老师的观点,感谢~

    作者回复: 你说的这种方式也是一种处理方式,大多作为模块间调用的防御式编程(防止其他模块的运行时异常给本模块带来致命的错误)。这要看你的控制流是怎么设计的了,也就是,在各种错误和异常出现时,控制流的交还和转换。这方面需要你仔细考虑。切忌不要滥用panic,需要同时考虑程序的稳定性、易用性、便捷度和使用体验。 如果调用层次太深的话,最好是用error的方式来做,一层一层返回error,然后在需要处理对应错误的那一层处理掉这个error。至于panic的话,一般情况下不要用,除非程序遇到了不可恢复的错误。 如果是一个模块遇到了不可恢复的错误,那么可以用panic把这种致命的错误(也就是panic,运行时恐慌)给到使用这个模块的程序层,然后再在这个程序层上recover这个panic,再然后,可以重新调用模块(重试),也可以把panic转换为error再往上报告,甚至还可以重新包装panic重新抛出(也就是继续沿着调用栈向上抛出panic),同时酌情打印详细日志记录下来,至于具体怎么做就看你怎么设计了。

    2023-09-11归属地:四川
    2
  • Geek_37a441
    老师,你好,我想问下,panic触发的时机,比如指令执行过程中,在什么时候会调用到相关的panic,比如数组越界是什么时候调用runtime.panicIndex,是有个额外的线程不断检测有异常了吗?

    作者回复: 当然不是了,是执行程序的程序碰到严重错误就抛出来,属于Go运行时系统的职责范围。

    2020-12-15
    4
  • 江山如画
    一个函数如果要把 panic 转化为error类型值,并将其结果返回给调用方,可以考虑把 defer 语句封装到一个匿名函数之中,下面是实验的一个例子,所用函数是一个除法函数,当除数为0的时候会抛出 panic并捕获。 func divide(a, b int) (res int, err error) { func() { defer func() { if rec := recover(); rec != nil { err = fmt.Errorf("%s", rec) } }() res = a / b }() return } func main() { res, err := divide(1, 0) fmt.Println(res, err) // 0 runtime error: integer divide by zero res, err = divide(2, 1) fmt.Println(res, err) // 2 <nil> }
    2018-10-08
    3
    37
  • Bang
    先使用go中的类似try catch这样的语句,将异常捕获的异常转为相应的错误error就可以了
    2018-09-28
    14
  • 冰激凌的眼泪
    panic时,会捕获异常及异常上下文(函数名+文件行) 类似看作有一个异常上下文列表,始于异常触发处,沿着函数调用逆向展开,每一级append自己的异常上下文,直至goroutine入口函数,最终被runtime捕获 最终异常信息被打印,异常上下文列表被顺序打印,程序退出
    2018-10-02
    4
  • 🐻
    https://gist.github.com/bwangelme/9ce1c606ba9f69c72f82722adf1402e1
    2019-03-03
    2
收起评论
显示
设置
留言
16
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部