panic 和 recover
當然能觸發程序宕機退出的,也可以是我們自己,比如經過檢查判斷,當前環境無法達到我們程序進行的預期條件時(比如一個服務指定監聽端口被其他程序占用),可以手動觸發 panic,讓程序退出停止運行。
1. 觸發panic
手動觸發宕機,是非常簡單的一件事,只需要調用 panic 這個內置函數即可,就像這樣子
package mainfunc main() {panic("crash")
}
運行結果:
panic: crashgoroutine 1 [running]:
main.main()d:/Goworks/src/尚硅谷/異常處理/demo01.go:4 +0x25
exit status 2
2. 捕獲 panic
發生了異常,有時候就得捕獲,就像 Python 中的except
一樣,那 Golang 中是如何做到的呢?
這就不得不引出另外一個內建函數 – recover
,它可以讓程序在發生宕機后起生回生。
但是 recover 的使用,有一個條件,就是它必須在 defer 函數中才能生效,其他作用域下,它是不工作的。
這是一個簡單的例子
package mainimport "fmt"func set_data(x int) {defer func() {// recover() 可以將捕獲到的panic信息打印if err := recover(); err != nil {fmt.Println(err)}}()// 故意制造數組越界,觸發 panicvar arr [10]intarr[x] = 88
}func main() {set_data(20)// 如果能執行到這句,說明panic被捕獲了// 后續的程序能繼續運行fmt.Println("everything is ok")
}
運行結果:
捕獲到panic后正常運行。
runtime error: index out of range [20] with length 10
everything is ok
3. 無法跨協程
從上面的例子,可以看到,即使 panic 會導致整個程序退出,但在退出前,若有 defer 延遲函數,還是得執行完 defer 。
但是這個 defer 在多個協程之間是沒有效果,在子協程里觸發 panic,只能觸發自己協程內的 defer,而不能調用 main 協程里的 defer 函數的。
來做個實驗就知道了
package mainimport ("fmt""time"
)func main() {// 這個 defer 并不會執行defer fmt.Println("in main")go func() {defer println("in goroutine")// 這個panic就會終止程序// 在這終止了外面的defer也不會執行panic("")}()time.Sleep(2 * time.Second)
}
輸出如下,并沒有執行defer fmt.Println(“in main”)
in goroutine
panic:goroutine 19 [running]:
main.main.func1()d:/Goworks/src/尚硅谷/異常處理/demo03.go:14 +0x3e
created by main.main in goroutine 1d:/Goworks/src/尚硅谷/異常處理/demo03.go:12 +0x59
exit status 2
4. 總結
Golang 異常的拋出與捕獲,依賴兩個內置函數:
- panic:拋出異常,使程序崩潰
- recover:捕獲異常,恢復程序或做收尾工作(通常來說,不應該對進入 panic 宕機的程序做任何處理,但有時,需要我們可以從宕機中恢復,至少我們可以在程序崩潰前,做一些操作,舉個例子,當 web 服務器遇到不可預料的嚴重問題時,在崩潰前應該將所有的連接關閉,如果不做任何處理,會使得客戶端一直處于等待狀態,如果 web 服務器還在開發階段,服務器甚至可以將異常信息反饋到客戶端,幫助調試。)
revocer 調用后,拋出的 panic 將會在此處終結,不會再外拋,但是 recover,并不能任意使用,它有強制要求,必須得在 defer 下才能發揮用途。