为什么Golang中defer调用的自定义函数无法通过recover捕获panic?
问题表现
两段Go代码的panic捕获行为存在明显差异:
- 第一段代码将recover逻辑封装为独立函数
myRecover,在defer的匿名函数中调用该封装函数,运行时无法捕获panic,程序会直接崩溃
package main import "fmt" func myRecover() { if r := recover(); r != nil { fmt.Println(r) } } func main() { defer func() { myRecover() }() panic("The gas price is skyrocketing!") }
- 第二段代码直接将recover逻辑写在defer绑定的匿名函数内部,运行时可以正常捕获panic,打印panic信息后程序正常退出
package main import "fmt" func main() { defer func() { if r := recover(); r != nil { fmt.Println(r) } }() panic("The gas price is skyrocketing!") }
核心原理
出现这个差异,本质是Go语言对recover()的生效条件做了严格限制,规则明确写在Go语言官方规范里:
只有当
recover()被直接调用在defer语句直接绑定的函数体内时,才能拦截当前goroutine的panic,其余所有场景下调用recover()只会返回nil,不会产生任何捕获效果。
从运行时的执行逻辑看:
- panic触发后,Go运行时不会立刻让程序崩溃,而是会从当前触发panic的函数开始,逆序遍历执行当前栈上所有已经注册的defer函数
- 每执行完一个defer直接绑定的函数,运行时会做一次检查:这个刚执行完的函数里,有没有直接调用
recover()?如果有,就停止panic传播流程,把记录的panic信息返回给对应的recover()调用点,程序继续正常运行;如果没有,就继续往上层调用栈走,执行上一层函数注册的defer - 第一段代码中,defer直接绑定的是外层的匿名函数,
recover()实际是在这个匿名函数调用的子函数myRecover()里执行的,不属于defer绑定函数的直接调用。运行时检查匿名函数的执行逻辑时,没找到直接的recover()调用,就不会触发拦截逻辑,myRecover里执行的recover()自然只能拿到nil,起不到捕获作用 - 第二段代码中,
recover()直接写在defer绑定的匿名函数内部,完全满足生效要求,所以可以正常拿到panic信息,阻止程序崩溃。
内容的提问来源于stack exchange,提问作者Wang Honghui
相关产品推荐
相关产品推荐

