Go 異常處理機制 panic 和 recover
recover
使用 panic 拋出異常後, 將立即停止當前函數的執行並運行所有被 defer 的函數,然後將 panic 拋向上一層,直至程序 crash。但是也可以使用被 defer 的 recover 函數來捕獲異常阻止程序的崩潰,recover 只有被 defer 後纔是有意義的。
func main() {
print(123)
print(456)
panic("throw an error")
print(678) //IDE會有提示: Unreachable code
}
結果:
123456panic: throw an error
goroutine 1 [running]:
main.main()
/Users/shuangcui/explore/panicandrecover.go:31 +0x67
使用 recover() 捕獲異常:
func main() {
print(123)
defer func() {
if err := recover(); err != nil {
print("recover it")
}
}()
print(456)
panic("throw an error")
print(678) //IDE會有提示: Unreachable code
}
結果爲:
123456recover it
如果有兩個 recover, 則捕獲異常的是後一個
func main() {
print(123)
defer func() {
if err := recover(); err != nil {
print("recover it")
}
}()
defer func() {
if err := recover(); err != nil {
print("復原!")
}
}()
print(456)
panic("throw an error")
print(678) //IDE會有提示: Unreachable code
}
結果爲:
123456復原!
panic 之後的任何代碼都不會繼續執行
前提是 panic 不在 if 裏面
package main
import "fmt"
func main() {
defer_call()
fmt.Println("333 Helloworld")
}
func defer_call() {
defer func() {
fmt.Println("11111")
}()
defer func() {
fmt.Println("22222")
}()
defer func() {
if r := recover(); r != nil {
fmt.Println("Recover from r : ", r)
}
}()
defer func() {
fmt.Println("33333")
}()
fmt.Println("111 Helloworld")
panic("Panic 1!")
//使用panic拋出異常後, 將立即停止當前函數的執行並運行所有被defer的函數,然後將panic拋向上一層, 直至程序crash
//但是也可以使用被defer的recover函數來捕獲異常阻止程序的崩潰,recover只有被defer後纔是有意義的。
panic("Panic 2!") //panic1之後的panic2沒有任何機會會被執行, panic2之後的任何代碼更沒有任何機會被執行
fmt.Println("222 Helloworld")
}
輸出爲:
111 Helloworld
33333
Recover from r : Panic 1!
22222
11111
333 Helloworld
對於 goroutine 中的 panic, 協程外面的 recover 是無法恢復的;goroutine 中的 recover, 同樣無法恢復協程外的 panic
但協程中的 recover 可以恢復協程中的 panic
package main
import (
"fmt"
"time"
)
func main() {
go func() {
defer func() {
if err := recover(); err != nil {
fmt.Println("recover err:", err)
}
}()
panic("裏面出錯了")
}()
//panic("外面出錯了")
time.Sleep(1 * time.Second)
}
輸出爲:
recover err 裏面出錯了
主方法中的 recover, 也可以恢復子方法裏的 panic
但如果go subfunc()
, 則同樣無法捕獲 subfunc 中的異常
func main() {
fmt.Println(123)
defer fmt.Println(999)
defer func() {
if err := recover(); err != nil {
fmt.Println("恢復異常:",err)
}
}()
subfunc()
}
func subfunc() {
defer fmt.Println(888)
panic("出現了bug")
defer fmt.Println(456)
}
結果爲:
123
888
恢復異常: 出現了bug
999
因爲 panic 發生的時候,panic 函數後面的語句都不會執行了,所以 recover 函數不能放在 panic 語句後面執行,而要放在 defer 函數中執行。
使用 panic 拋出異常後,函數執行將從調用 panic 的地方停止,如果函數內有 defer 調用,則執行 defer 後邊的函數調用,如果 defer 調用的函數中沒有捕獲異常信息,這個異常會沿着函數調用棧往上傳遞,直到 main 函數仍然沒有捕獲異常,將會導致程序異常退出
如何區別使用 panic 和 error 兩種方式?
慣例是:導致關鍵流程出現不可修復性錯誤的使用 panic ,其他使用 error 。
panic 和 recover 的組合有如下特性:
-
有 panic 沒 recover ,程序宕機。
-
有 panic 也有 recover ,程序不會宕機,執行完對應的 defer 後,從宕機點退出當前函數後繼續執行。
recover 能捕獲所有錯誤嗎?
不能!
像
-
併發讀寫 map
fatal error: concurrent map read and map write
-
堆棧內存耗盡 (如遞歸)
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0200e1bf0 stack=[0xc0200e0000, 0xc0400e0000]
fatal error: stack overflow
-
將 nil 函數作爲 goroutine 啓動
fatal error: go of nil func value
-
goroutines 死鎖
fatal error: all goroutines are asleep - deadlock!
-
線程超過設置的最大限制
fatal error: thread exhaustion
-
超出可用內存
fatal error: runtime: out of memory
總之 都會報 fatal error:xxxxxxxx
拓展 & 參考:
golang panic 和 recover 實現原理 [1]
Go 學習筆記(19)— 函數(05)[如何觸發 panic、觸發 panic 延遲執行、panic 和 recover 的關係][2]
Go 語言踩坑記——panic 與 recover[3]
參考資料
[1]
golang panic 和 recover 實現原理: https://blog.csdn.net/u010853261/article/details/102761955
[2]
Go 學習筆記(19)— 函數(05)[如何觸發 panic、觸發 panic 延遲執行、panic 和 recover 的關係]: https://blog.csdn.net/wohu1104/article/details/105571916
[3]
Go 語言踩坑記——panic 與 recover: https://xiaomi-info.github.io/2020/01/20/go-trample-panic-recover/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/n1LGh2QFi4QWmGD_M3hgHQ