1 概述
在go中,經常看到 panic 和 recover 與 defer配套使用。我們具體來看。
2 panic
panic的英文是恐慌。下面是panic方法的官方介紹,它會中止當前協程的正常執行。當一個方法F調用panic時,F的正常執行立即停止。任何被 F defer的方法,將會以正常的方式執行,然後F返回給它的調用方。
// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated with a non-zero exit code. This
// termination sequence is called panicking and can be controlled by the
// built-in function recover.
func panic(v any)
import (
"fmt"
)
func main() {
A()
return
}
func A() {
fmt.Println("A before panic")
panic("A panic")
fmt.Println("A after panic")
return
}
執行結果:
A before panic
panic: A panic
goroutine 1 [running]:
main.A()
/Users/yiyunjie/go/src/Test/main.go:14 +0x68
main.main()
/Users/yiyunjie/go/src/Test/main.go:8 +0x1c
Process finished with the exit code 2
我們可以看到,方法A正常地執行了第一個打印以及panic,之後協程中止運行, 後續的代碼未按正常流程執行,協程以非正常方式退出,退出碼爲2。
很顯然,在保持服務可用性的角度來看,panic並不是一種友好的退出方式,有沒有一種更溫和的處理方式呢?答案就是 defer 和 recover。
3 recover
我們在上一篇文章中講過,defer會在函數return之前執行,但是panic很顯然並不是return,那怎麼辦呢?這裏就用到 recover。
recover 可以中止panic造成的協程非正常退出,從它的名稱 恢復 就能看出一二,相當於給panic造了一個兜底的安全網。我們來看看。
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
func recover() any
示例:
package main
import (
"fmt"
)
func main() {
A()
return
}
func A() {
r := recover()
fmt.Println(fmt.Sprintf("recover r:%+v", r))
fmt.Println("A before panic")
panic("A panic")
fmt.Println("A after panic")
return
}
輸出
recover r:<nil>
A before panic
panic: A panic
goroutine 1 [running]:
main.A()
/Users/yiyunjie/go/src/Test/main.go:17 +0xdc
main.main()
/Users/yiyunjie/go/src/Test/main.go:8 +0x1c
Process finished with the exit code 2
很明顯,如果直接把recover定義在函數體裏面,它並不會中止panic,而是像正常代碼一樣順序執行。所以我們必須要用到 defer,再試一次。
package main
import (
"fmt"
)
func main() {
A()
return
}
func A() {
defer func() {
r := recover()
fmt.Println(fmt.Sprintf("recover r: %+v, type: %T", r, r))
}()
fmt.Println("A before panic")
panic("A panic")
fmt.Println("A after panic")
return
}
輸出
A before panic
recover r: A panic, type: string
Process finished with the exit code 0
這次協程正常地退出了,recover 輸出的內容,正好就是 panic 輸入的內容。
4 改造
我們獲取到了panic的內容,想要對它做一些處理,再return給調用方,應該怎麼做呢?
(待續)