上一期我们讲了 Go 中的错误处理,这一期讲讲 Go 中的 异常 处理。错误和异常是两个不同的概念,非常容易混淆。错误指的是可能出现问题的地方出现了问题;而异常指的是不应该出现问题的地方出现了问题。
在有些情况,当程序发生异常时,无法继续运行。在这种情况下,我们会使用 panic
来终止程序。当函数发生 panic
时,它会终止运行,在执行完所有的延迟函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic
信息,接着打印出堆栈跟踪,最后程序终止。
我们应该尽可能地使用错误,而不是使用 panic
和 recover
。只有当程序不能继续运行的时候,才应该使用 panic
和 recover
机制。
panic
有两个合理的用例:
- 发生了一个不能恢复的错误,此时程序不能继续运行。一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用
panic
,因为如果不能绑定端口,啥也做不了。 - 发生了一个编程上的错误。假如我们有一个接收指针参数的方法,而其他人使用
nil
作为参数调用了它。在这种情况下,我们可以使用 panic
,因为这是一个编程错误:用 nil
参数调用了一个只能接收合法指针的方法。
下面是内建函数 panic
的签名:
func panic(v interface{})
当程序终止时,会打印传入 panic
的参数。
package main
func main() {
panic("runtime error")
}
运行上面的程序,会打印出传入 panic
函数的信息,并打印出堆栈跟踪:
panic: runtime error
goroutine 1 [running]:
main.main()
(...)/main.go:4 +0x27
上面已经提到了,当函数发生 panic
时,它会终止运行,在执行完所有的延迟函数后,程序控制返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic
信息,接着打印出堆栈跟踪,最后程序终止。下面通过一个简单的例子看看是不是这样:
package main
import "fmt"
func myTest() {
defer fmt.Println("defer in myTest")
panic("runtime error")
}
func main() {
defer fmt.Println("defer in main")
myTest()
}
运行该程序后输出如下:
defer in myTest
defer in main
panic: runtime error
goroutine 1 [running]:
main.myTest()
(...)/main.go:7 +0x73
main.main()
(...)/main.go:11 +0x70
recover
是一个内建函数,用于重新获得 panic
协程的控制。下面是内建函数 recover
的签名:
func recover() interface{}
recover
必须在 defer
函数中才能生效,在其他作用域下,它是不工作的。在延迟函数内调用 recover
,可以取到 panic
的错误信息,并且停止 panic
续发事件,程序运行恢复正常。下面是网上找的一个例子:
package main
import "fmt"
func myTest(x int) {
defer func() {
// recover() 可以将捕获到的 panic 信息打印
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var array [10]int
array[x] = 1
}
func main() {
// 故意制造数组越界 触发 panic
myTest(20)
// 如果能执行到这句 说明 panic 被捕获了
// 后续的程序能继续运行
fmt.Println("everything is ok")
}
虽然该程序触发了 panic
,但由于我们使用了 recover()
捕获了 panic
异常,并输出 panic
信息,即使 panic
会导致整个程序退出,但在退出前,有 defer
延迟函数,还是得执行完 defer
。然后程序还会继续执行下去:
runtime error: index out of range [20] with length 10
everything is ok
这里要注意一点,只有在相同的协程中调用 recover
才管用, recover
不能恢复一个不同协程的 panic
。
package main
import (
"fmt"
"time"
)
func main() {
// 这个 defer 并不会执行
defer fmt.Println("in main")
go func() {
defer println("in goroutine")
panic("")
}()
time.Sleep(time.Second)
}
运行后输出如下:
in goroutine
panic:
goroutine 19 [running]:
main.main.func1()
(...)/main.go:13 +0x7b
created by main.main
(...)/main.go:11 +0x79
参考文献:
[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.