go 語言的宕機恢復(recover)


go 語言中的三種報錯機制

  • 第一種,也就是常說的 error 類型錯誤,不需要 recover 拯救,處理方式自己決定

  • 第二種,panic 和 recover 是緊密集合的,有點類似 try catch,recover 能捕獲到 panic

  • 第三種,一些 Go 語言系統級別的錯誤,比如發生死鎖,數據競爭,這種錯誤程序會立刻報錯,無法 recover

recove 的作用

go 語言中,錯誤一般會由 error 觸發,但是如果比較嚴重的錯誤(通常是沒有恰當處理的 error,也可是手動觸發) 會造成 panic 。 一旦主程序 panic ,會導致整個程序掛掉。如果這個錯誤不是那么嚴重,我們希望程序可以繼續往下執行,而不是整個程序掛掉。

  1. recover 函數,對 panic 錯誤進行攔截,避免上傳給主函數,進而避免整個程序掛掉。
  2. 可以在程序崩潰前,做一些操作,舉個例子,當 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
}

知識點

  1. recover 僅在延遲函數 defer 中有效
  2. 因為 Go 語言沒有異常系統,其使用 panic 觸發宕機類似於其他語言的拋出異常,recover 的宕機恢復機制就對應其他語言中的 try/catch 機制。
  3. 謹記一點 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
  • 日志能不能輸出這條錯誤,要看這條日志能不能走到。

參考文獻

  1. https://www.zhihu.com/question/371695315/answer/1026803101

  2. http://c.biancheng.net/view/64.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM