介绍
在golang的设计思想中,panic是用于在已知的错误发生时中断程序运行,比如空指针,数组越界等,在golang的创始人眼中,这些错误都是不被允许的,因此只要发生,就需要中断程序运行,然而在实际的应用中,我们并不总是希望panic的出现使服务崩溃,因此golang提供了recover函数,可以用于捕捉panic,将其转换为一般的错误进行处理。本文主要介绍panic与recover 之间的关系。
在了解panic与recover之前,需要先了解golang的defer机制,可以参考前一篇文章 了解defer
Panic函数
Panic函数的实现:runtime/panic.go gopanic函数
panic对应结构体
1 | type _panic struct { |
panic内部实现流程
用文字描述panic的处理流程:
- 获取当前panic所在的goroutine标识,拿到goroutine结构体 gp
- 生成一个panic结构体 p,将 gp.panic指向p
- 遍历当前goroutine已注册的defer函数集合,获取集合中的第一个defer
- 如果没有defer函数,结束循环,打印 gp.panic
- 如果有defer函数 d,判断此defer函数是否执行过
- 如果已经执行过
- 判断 defer结构体 d的 panic变量是否为空,不为空,则设置 此panic需要强制退出
- 设置 d.panic=nil;如果defer函数并不是 open-coded defer,设置 d.fn=nil (d.fn就是defer对应的执行函数); 将 defer函数的下一个defer函数 设置给 gp.defer,释放这个defer函数,结束此次循环
- 如果没有执行过
- 设置 d.started=true,标识 这个defer函数已经执行了
- 将 d.panic指向 前面生成的 panic结构体 p
- 如果 不是 open-coded defer,则执行 defer对应的函数;(将defer结构体对应的变量全部传入了,包括panic指针),将 p的 argp设置为 defer结构体的对应变量地址
- 设置 d.panic=nil; 设置defer.fn = nil; 将 defer函数的下一个defer函数 设置给 gp.defer,释放这个defer函数
- 如果panic p已经被recover了,则将panic链条上的全部recover
- 结束此次循环
- 如果已经执行过
- 循环结束后,处理gp.panic
recover函数
recover函数的实现: runtime/panic.go gorecover()
1 | func gorecover(argp uintptr) interface{} { |
gorecover(argp uintptr)中的 argp是调用recover函数的参数地址,通常是defer函数的参数地址
常见问题
1、嵌套的defer中无法recover panic
1 | func main() { |
原因
根据前面 panic的实现流程,panic会生成一个变量p,设置p.argp为第一个defer相关的参数地址指针;而执行defer时,gorecover()方法中的参数 argp时第二个defer相关的参数地址指针,因此无法满足条件,无法recover;
2、recover函数包装后无法生效
1 | func main() { |
原因
根据前面 panic的实现流程,panic会生成一个变量p,设置p.argp为第一个defer相关的属性变量指针;
而执行recover时,gorecover()方法中的参数 argp时RecoverPanic函数的参数地址,因此无法满足条件,无法recover;
- 本文作者: xczll
- 本文链接: https://xczllgit.github.io/2021/08/07/golang/2021-08-07-panic-recover/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!