Ch03-GoLang 之 panic 和 recover
October 1, 2024
panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer; recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
type _panic struct {
argp unsafe.Pointer // 指向 defer 调用时参数的指针
arg interface{} // 调用 panic 时传入的参数
link *_panic // 指向了更早调用的 runtime._panic 结构
recovered bool // 表示当前 panic 是否被 recover 恢复(调用 recover 函数后,这里会被标记)
aborted bool // 表示当前的 panic 是否被强行终止
pc uintptr
sp unsafe.Pointer
goexit bool
}
基本原理 #
panic
编译器会将关键字 panic 转换成 runtime.gopanic,该函数的执行过程包含以下几个步骤:
- 创建新的 runtime._panic 并添加到所在 Goroutine 的 _panic 链表的最前面;
- 在循环中不断从当前 Goroutine 的 _defer 中链表获取 runtime._defer 并调用 runtime.reflectcall 运行延迟调用函数;
- 调用 runtime.fatalpanic 中止整个程序;
recover
在 Go 语言的运行时实现中,recover 是通过汇编代码实现的,它依赖于特定平台的寄存器和栈布局来获取 goroutine 的状态信息。例如,在 src/runtime/asm_amd64.s 中,recover 函数通过特定的指令序列来检查和修改 goroutine 的状态,从而实现对 panic 的捕获和恢复。
recover 函数通过访问当前 goroutine 的内部状态来确定是否有 panic 正在传播。
- 如果有,它会从 _panic 结构体中获取传递给 panic 的值(arg 字段),然后将 goroutine 的状态从 panic 状态恢复为正常状态。接着,defer 函数继续执行,执行完毕后,程序从引发 panic 的函数调用之后的位置继续正常执行(因为 panic 被捕获并停止传播)。
- 如果 recover 在没有 panic 发生的情况下被调用,它会返回 nil,因为此时没有 panic 相关信息可供捕获。
recover 函数只有在 defer 函数内部调用才会生效。当一个 defer 函数被调用时,运行时会检查当前的 goroutine 是否处于 panic 状态。如果处于 panic 状态,并且 defer 函数中调用了 recover 函数,recover 函数会停止 panic 的传播。
常见的坑 #
跨协程失效
func main() {
defer println("in main")
go func() {
defer println("in goroutine")
panic("")
}()
time.Sleep(1 * time.Second)
}
当我们运行这段代码时会发现 main 函数中的 defer 语句并没有执行,执行的只有当前 Goroutine 中的 defer。
defer 关键字对应的 runtime.deferproc 会将延迟调用函数与调用方所在 Goroutine 进行关联。所以当程序发生崩溃时只会调用当前 Goroutine 的延迟调用函数也是非常合理的。
失效的崩溃恢复
func main() {
defer fmt.Println("in main")
if err := recover(); err != nil {
fmt.Println(err)
}
panic("unknown err")
}
recover 只有在发生 panic 之后调用才会生效。然而在上面的控制流中,recover 是在 panic 之前调用的,并不满足生效的条件,所以我们需要在 defer 中使用 recover 关键字。
嵌套崩溃
Go 语言中的 panic 是可以多次嵌套调用的。
func main() {
defer fmt.Println("in main")
defer func() {
defer func() {
panic("panic again and again")
}()
panic("panic again")
}()
panic("panic once")
}
可以打印出下述内容
in main
panic: panic once
panic: panic again
panic: panic again and again