独特的panic和recover
1 panic
Go 语言的错误处理方法比较特别,需要返回error
给调用者,但是如果遇到的是无法恢复的错误,返回error
也没有意义,此时就需要主动触发panic
。
但有些错误只能在运行时检查,如数组访问越界、空指针引用等,这些运行时错误会引发panic
异常。
panic
能够改变程序的控制流,调用panic
后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的defer
。
一个简单的例子:
func main() {
defer fmt.Println("defer 1")
defer fmt.Println("defer 2")
go func() {
defer fmt.Println("defer 4")
}()
panic("panic")
defer fmt.Println("defer 3")
}
$ go run main.go
defer 2
defer 1
panic: panic
goroutine 1 [running]:
main.main()
/home/user/go/src/panic/main.go:13 +0xfa
exit status 2
panic
仅保证当前 Goroutine 的panic
语句前的所有defer
能被执行,其他 Goroutine 的defer
不一定执行。
嵌套崩溃
panic
是可以多次嵌套调用的。
func main() {
defer fmt.Println("defer 1")
defer func() {
defer func() {
panic("panic 3")
}()
panic("panic 2")
}()
panic("panic 1")
}
$ go run main.go
defer 1
panic: panic 1
panic: panic 2
panic: panic 3
goroutine 1 [running]:
main.main.func1.1()
...
exit status 2
从上述程序输出的结果,我们可以确定程序多次调用panic
也不会影响defer
函数的正常执行,所以用defer
进行收尾工作一般来说都是安全的。
2 recover
一般情况下,对于主动触发的panic
不应该做任何处理,但有时,一些异常是可以进行处理的,或者可能需要在程序中止前做一操作。
recover
关键字的作用就是这个,能使程序从panic
中恢复,并返回panic
的值。
recover
只有在defer
中调用才能生效。
func test() int {
defer fmt.Println("defer")
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic("panic")
return 1
}
func main() {
fmt.Println(test())
fmt.Println("main is running")
}
$ go run main.go
panic
defer
0
recover
没有让程序异常退出,且defer
正常执行,panic
后的代码没有执行,test()
函数返回了零值。
3 panic+recover简化错误处理
func catchErr(num int) {
defer func() {
if err := recover(); err != nil {
fmt.Println("[recover]", err, num)
}
}()
panic("panic ")
}
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println("main recover: ", err)
}
}()
for i := 0; i < 3; i++ {
fmt.Println("main goroutine: ", i)
go catchErr(i)
time.Sleep(time.Second)
}
start:
goto start
}
$ go run main.go
main goroutine: 0
[recover] panic 0
main goroutine: 1
[recover] panic 1
main goroutine: 2
[recover] panic 2
为每个goroutine添加一个可以自动消化panic
的recover
,能避免整个程序的异常中断。