-
Notifications
You must be signed in to change notification settings - Fork 174
Description
Go 语言中两个经常成对出现的两个关键字 — panic 和 recover。这两个关键字与上一节提到的 defer 有紧密的联系,它们都是 Go 语言中的内置函数,也提供了互补的功能。
- panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 goroutine 中递归执行调用方的 defer;
- recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
panic和recover的一些行为现象
- panic 只能触发当前 goroutine 的 defer;
- recover 只有在 defer 中调用才会生效;
- panic 允许在 defer 中嵌套多次调用;
下面通过几个例子了解一下使用 panic 和 recover 关键字时遇到的这几个现象
panic 只能触发当前 goroutine 的 defer
panic 只会触发当前 Goroutine 的延迟函数调用,我们可以通过如下所示的代码了解该现象:
func main() {
defer println("in main")
go func() {
defer println("in goroutine")
panic("")
}()
time.Sleep(1 * time.Second)
}
$ go run main.go
in goroutine
panic:
...
当我们运行这段代码时会发现 main 函数中的 defer 语句并没有执行,执行的只有当前 Goroutine 中的 defer。
前面我们曾经介绍过 defer 关键字对应的 runtime.deferproc
会将延迟调用函数与调用方所在 goroutine 进行关联。所以当程序发生崩溃时只会调用当前 goroutine 的延迟调用函数也是非常合理的。
goroutine1 =====> _defer ---> _defer -> _defer
goroutine2 =====> _defer ---> _defer -> _defer
goroutine3 =====> _defer ---> _defer -> _defer
像上面示意这样,每个 goroutine 都有自己的延迟调用链,相互之间没有太多的关联,一个 goroutine 在 panic 时也不应该执行其他 goroutine 的延迟函数。
recover 只有在 defer 中调用才会生效
初学 Go 语言的读者可能会写出下面的代码,在主程序中调用 recover 试图中止程序的崩溃,但是从运行的结果中我们也能看出,下面的程序没有正常退出。
func main() {
defer fmt.Println("in main")
if err := recover(); err != nil {
fmt.Println(err)
}
panic("unknown err")
}
$ go run main.go
in main
panic: unknown err
goroutine 1 [running]:
main.main()
...
exit status 2
仔细分析一下这个过程就能理解这种现象背后的原因,recover 只有在发生 panic 之后调用才会生效。然而在上面的控制流中,recover 是在 panic 之前调用的,并不满足生效的条件,所以我们需要在 defer 中使用 recover 关键字。
func main() {
defer func() {
fmt.Println("defer in main")
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic("unknown err")
}
嵌套panic
Go 语言中的 panic 是可以多次嵌套调用的。一些熟悉 Go 语言的读者很可能也不知道这个知识点,如下所示的代码就展示了如何在 defer 函数中多次调用 panic:
func main() {
defer fmt.Println("in main")
defer func() {
defer func() {
panic("panic again and again")
}()
panic("panic again")
}()
panic("panic once")
}
$ go run main.go
in main
panic: panic once
panic: panic again
panic: panic again and again
goroutine 1 [running]:
...
exit code 2
从上述程序输出的结果,我们可以确定程序多次调用 panic 也不会影响 defer 函数的正常执行,所以使用 defer 进行收尾工作一般来说都是安全的。