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 的組合有如下特性:

recover 能捕獲所有錯誤嗎?

不能!

Go 有哪些無法恢復的致命場景?

runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0200e1bf0 stack=[0xc0200e0000, 0xc0400e0000]
fatal error: stack overflow

總之 都會報 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