go 語言中的三種報錯機制
-
第一種,也就是常說的 error 類型錯誤,不需要 recover 拯救,處理方式自己決定
-
第二種,panic 和 recover 是緊密集合的,有點類似 try catch,recover 能捕獲到 panic
-
第三種,一些 Go 語言系統級別的錯誤,比如發生死鎖,數據競爭,這種錯誤程序會立刻報錯,無法 recover
recove 的作用
在 go 語言中,錯誤一般會由 error 觸發,但是如果比較嚴重的錯誤(通常是沒有恰當處理的 error,也可是手動觸發) 會造成 panic 。 一旦主程序 panic ,會導致整個程序掛掉。如果這個錯誤不是那么嚴重,我們希望程序可以繼續往下執行,而不是整個程序掛掉。
- 用
recover函數,對panic錯誤進行攔截,避免上傳給主函數,進而避免整個程序掛掉。 - 可以在程序崩潰前,做一些操作,舉個例子,當 web 服務器遇到不可預料的嚴重問題時,在崩潰前應該將所有的連接關閉,如果不做任何處理,會使得客戶端一直處於等待狀態。
下面例子
如果給 out 函數傳入兩個相同的形參,就會引發 panic。
如果沒有 recover 攔截,fmt.Print 這行是執行不到的。
func main() {
Out(1, 1)
fmt.Println("*******此行函數依然能繼續執行******")
}
func Out(numb1, numb2 int) bool {
defer func() {
if r := recover(); r != nil { # 在此處對panic進行攔截,不會將錯誤繼續上報上去。
fmt.Println("異常已撲捉,避免繼續往上層傳遞")
}
}()
if numb1 == numb2 {
panic("兩個數不能相等")
}
return numb1 > numb2
}
知識點
- recover 僅在延遲函數
defer中有效 - 因為
Go語言沒有異常系統,其使用 panic 觸發宕機類似於其他語言的拋出異常,recover 的宕機恢復機制就對應其他語言中的 try/catch 機制。 - 謹記一點
recover只能恢復本協程的panic。
注意:即使是子協程內引發的 panic 依然會導致主程序的掛掉,如下面的例子
func main() {
go OutOne()
go OutTwo()
time.Sleep(time.Minute)
}
func OutOne() {
panic("錯誤")
}
func OutTwo() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Print("****此處繼續執行***")
}
}

進階
那么上面情況該怎么解決?
其實就是在發生 panic 的協程里面,用 recover 進行攔截。讓它傳不到主函數。
func main() {
go OutOne()
go OutTwo()
time.Sleep(time.Minute)
}
func OutOne() {
defer func() {
if r := recover(); r != nil {
fmt.Println("OutOne 的報錯已經別撲捉", r)
}
}()
panic("錯誤")
}
func OutTwo() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("****此處繼續執行***")
}
}
當panic、defer和子函數混合使用時的執行順序
func f() {
fmt.Println("打開文件b")
defer func() {
fmt.Println("關閉文件b")
}()
panic("文件b讀取異常")
}
func main() {
fmt.Println("打開文件a")
defer func() { fmt.Println("關閉文件a") }()
f()
fmt.Println("文件正常執行")
}
# 輸出
打開文件a
打開文件b
關閉文件b
關閉文件a
panic: 文件b讀取異常
- 記住這個例題,當
go語言發生panic時,會首先執行已經“進棧”的defer函數,最后然后在報出panic的錯誤。傳給上層的函數。 - 當子函數發生
panic的時候,函數依然是會先執行已經“進棧”的defer函數,而爆出panic。
defer
在 go的代碼中,defer 有點類似於壓棧操作,只有被執行到了,才會入棧(延遲執行),但是實際上,defer函數是編譯過程中,通過調整代碼順序實現
錯誤日志如何查看
package main
import "log"
func main() {
err := funcA()
if err != nil {
log.Println("日志輸出錯誤")
}
}
func funcA() error {
panic("錯誤")
}
輸出:

- 觀察錯誤輸出順序,錯誤就是一個入棧的過程,會從底到上的打印,直到遇到
recover。 - 日志能不能輸出這條錯誤,要看這條日志能不能走到。
