【Go學習】GO語言異常處理機制panic和recover分析
Golang 有2個內置的函數 panic() 和 recover(),用以報告和捕獲運行時發生的程序錯誤,與 error 不同,panic-recover 一般用在函數內部。一定要注意不要濫用 panic-recover,可能會導致性能問題,我一般只在未知輸入和不可靠請求時使用。
golang 的錯誤處理流程:當一個函數在執行過程中出現了異常或遇到 panic(),正常語句就會立即終止,然後執行 defer 語句,再報告異常信息,最後退出 goroutine。如果在 defer 中使用了 recover() 函數,則會捕獲錯誤信息,使該錯誤信息終止報告。
示例:
1 package main
2
3 import (
4 "fmt"
5 )
6
7 func main() {
8 f()
9 fmt.Println("Returned normally from f.")
10 }
11
12
13 func f() {
14 fmt.Println("Calling g.")
15 g()
16 fmt.Println("Returned normally from g.")
17 }
18
19 func g() {
20 panic("ERROR")
21 }
22
運行結果:
Calling g.
panic: ERROR
goroutine 1 [running]:
main.g()
/home/james_xie/work/golangstudy/integer.go:20 +0x39
main.f()
/home/james_xie/work/golangstudy/integer.go:15 +0x70
main.main()
/home/james_xie/work/golangstudy/integer.go:8 +0x22
exit status 2
上面函數的函數g中的panic("ERROR")
語句導致程序panic,正常語句就會立即終止,由於沒有defer語句,直接報告異常信息,然後退出整個goroutine ,可以通過panic的打印,看到程序在第20行的時候退出。下面修改一下,增加defer語句:
package main
import (
"fmt"
)
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func(){
fmt.Println("Defer in f")
}()
fmt.Println("Calling g.")
g()
fmt.Println("Returned normally from g.")
}
func g() {
panic("ERROR")
}
運行結果:
Calling g.
Defer in f
panic: ERROR
goroutine 1 [running]:
main.g()
/home/james_xie/work/golangstudy/integer.go:24 +0x39
main.f()
/home/james_xie/work/golangstudy/integer.go:19 +0x90
main.main()
/home/james_xie/work/golangstudy/integer.go:8 +0x22
exit status 2
這個時候可以看到,在報告異常信息之前,先執行了defer語句,然後再報告異常信息。下面我們在稍微修整下代碼:
package main
import (
"fmt"
)
func main() {
defer func(){
fmt.Println("Defer before f")
}()
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func(){
fmt.Println("Defer in f")
}()
fmt.Println("Calling g.")
g()
fmt.Println("Returned normally from g.")
}
func g() {
panic("ERROR")
}
運行結果如下:
Calling g.
Defer in f
Defer before f
panic: ERROR
goroutine 1 [running]:
main.g()
/home/james_xie/work/golangstudy/integer.go:27 +0x39
main.f()
/home/james_xie/work/golangstudy/integer.go:22 +0x90
main.main()
/home/james_xie/work/golangstudy/integer.go:11 +0x42
exit status 2
通過這個結果我們可以看出,defer語句的執行順序是逆序的,類似於數據結構中的棧(Stack)—-先進後出,我們繼續修改下代碼,添加對異常的處理,看看又會是個什麼情況:
package main
import (
"fmt"
)
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func(){
fmt.Println("Defer in f")
if r := recover(); r != nil {
fmt.Println("Recover panic : ",r)
}
}()
fmt.Println("Calling g.")
g()
fmt.Println("Returned normally from g.")
}
func g() {
panic("ERROR")
}
運行結果:
Calling g.
Defer in f
Recover panic : ERROR
Returned normally from f.
通過上面的運行結果可以看出,如果在 defer 中使用了 recover() 函數,則會捕獲錯誤信息,使該錯誤信息終止報告。然後程序繼續運行,注意程序中這條語句fmt.Println("Returned normally from g.")
沒有執行,由於在函數g中遇到panic函數,正常語句就會立即終止,所以函數g後面的語句不會執行,直接執行 defer 語句,在defer中通過recover()函數來捕獲錯誤信息,我們還是繼續修改代碼,看看又會出現什麼不同結果:
package main
import (
"fmt"
)
func main() {
defer func(){
fmt.Println("Defer before f")
}()
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func(){
fmt.Println("Defer in f")
if r := recover(); r != nil {
fmt.Println("Recover panic : ",r)
}
}()
fmt.Println("Calling g.")
g()
fmt.Println("Returned normally from g.")
}
func g() {
panic("ERROR")
}
運行結果:
Calling g.
Defer in f
Recover panic : ERROR
Returned normally from f.
Defer before f
其實這個修改沒多大意義,因爲在函數f中的defer語句中,通過recover()函數來捕獲錯誤信息,使該錯誤信息終止報告,後面的程序正常執行,defer語句正常是在函數數返回前執行一些操作,所以這個時候的main函數中的defer語句的執行是在fmt.Println("Returned normally from f.")
打印語句之後,main函數返回前執行的,最後我們在修改下代碼:
package main
import (
"fmt"
)
func main() {
defer func(){
fmt.Println("Defer before f")
if r := recover(); r != nil {
fmt.Println("Recover panic : ",r)
}
}()
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func(){
fmt.Println("Defer in f")
}()
fmt.Println("Calling g.")
g()
fmt.Println("Returned normally from g.")
}
func g() {
panic("ERROR")
}
運行結果:
Calling g.
Defer in f
Defer before f
Recover panic : ERROR
上門的運行結果可以驗證,如果不調用recover()函數來捕獲錯誤信息,其則會一直報告錯誤信息,可以對比上門的修改來對比看。
最後可以總結如下:
panic:
1、內建函數
2、假如函數F中書寫了panic語句,會終止其後要執行的代碼,在panic所在函數F內如果存在要執行的defer函數列表,按照defer的逆序執行
3、返回函數F的調用者G,在G中,調用函數F語句之後的代碼不會執行,假如函數G中存在要執行的defer函數列表,按照defer的逆序執行
4、直到goroutine整個退出,並報告錯誤
recover:
1、內建函數
2、用來控制一個goroutine的panicking行爲,捕獲panic,從而影響應用的行爲
3、一般的調用建議
a). 在defer函數中,通過recever來終止一個gojroutine的panicking過程,從而恢復正常代碼的執行
b). 可以獲取通過panic傳遞的error