本篇內容介紹了“Go編程中recover源碼是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
企業建站必須是能夠以充分展現企業形象為主要目的,是企業文化與產品對外擴展宣傳的重要窗口,一個合格的網站不僅僅能為公司帶來巨大的互聯網上的收集和信息發布平臺,創新互聯面向各種領域:成都邊坡防護網等成都網站設計、成都全網營銷推廣解決方案、網站設計等建站排名服務。
就像我們之前針對panic做的一樣,我們也寫一段簡單的代碼,通過其匯編碼嘗試找出內置函數recover()的底層實現。
編寫以下簡單的代碼,并保存在名為compile.go的文件里:
// recover/compile.go package recover func compile() { defer func() { recover() }() }
然后使用以下命令編譯代碼:
go tool compile -S recover/compile.go
接著根據代碼行號找出recover()語句對應的匯編碼:
0x0024 00036 (recover/compile.go:5) PCDATA $0, $1 0x0024 00036 (recover/compile.go:5) PCDATA $1, $0 0x0024 00036 (recover/compile.go:5) LEAQ ""..fp+40(SP), AX 0x0029 00041 (recover/compile.go:5) PCDATA $0, $0 0x0029 00041 (recover/compile.go:5) MOVQ AX, (SP) 0x002d 00045 (recover/compile.go:5) CALL runtime.gorecover(SB)
我們可以看到recover()函數調用被替換成了runtime.gorecover()函數。runtime.gorecover()實現源碼位于src/runtime/panic.go。
runtime.gorecover()函數實現很簡短:
func gorecover(argp uintptr) interface{} { gp := getg() // 獲取panic實例,只有發生了panic,實例才不為nil p := gp._panic // recover限制條件 if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) { p.recovered = true return p.arg } return nil }
短短的代碼,蘊含的信息量卻很大。 它可以解釋以下問題:
recover()到底是如何恢復panic的?
為什么recover()一定要在defer()函數中才生效?
假如defer()函數中調用了函數A(),為什么A()中的recover()不能生效?
runtime.gorecover()函數通過協程數據結構中的_panic得到當前的panic的實例(上面代碼中p),如果當前panic的狀態支持recover,給該panic實例標記recovered狀態(p.recovered = true),最后返回panic()函數的參數(p.arg)。
另外,當前執行recover()的defer函數是被runtime.gopanic()執行的,defer函數執行結束以后,runtime.gopanic()函數中會檢查panic實例的recovered狀態,如果發現panic被恢復,runtime.gopanic()將會結束當前panic流程,將程序流程恢復正常。
通過代碼的if語句可以看到需要滿足四個條件才可以恢復panic,且四個條件缺一不可:
p != nil:必須存在panic;
!p.goexit:非runtime.Goexit();
!p.recovered:panic還未被恢復;
argp == uintptr(p.argp):recover()必須被defer()直接調用。
當前協程沒有產生panic時,協程結構體中panic的鏈表為空,不滿足恢復條件。
當程序運行runtime.Goexit()時也會創建一個panic實例,會標記該實例的goexit屬性為true,但該類型的panic不能被恢復。
假設函數包含多個defer函數,前面的defer通過recover()消除panic后,函數中剩余的defer仍然會執行,但不能再次recover(),如下代碼所示,函數第一行defer中的recover()將返回nil。
func foo() { defer func() {recover()}() // 恢復無效,因為_panic.recovered = true defer func() {recover()}() // 標記_panic.recovered = true panic("err") }
細心的讀者或許會發現,內置函數recover()沒有參數,runtime.gorecover()函數卻有參數,為什么呢? 這正是為了限制recover()必須被defer()直接調用。
runtime.gorecover()函數的參數為調用recover()函數的參數地址,通常是defer函數的參數地址,同地_panic實例中也保存了當前defer函數的參數地址,如果二者一致,說明recover()被defer函數直接調用。舉例如下:
func foo() { defer func() { // 假設函數為A func() { // 假設函數為B // runtime.gorecover(B),傳入函數B的參數地址 // argp == uintptr(p.argp) 檢測失敗,無法恢復 if err := recover(); err != nil { fmt.Println("A") } }() }() }
通過以上源碼的分析,我們可以很好地回答以下問題了:
為什么recover()一定要在defer()函數中才生效?
假如defer()函數中調用了函數A(),為什么A()中的recover()不能生效?
如果recover()不在defer()函數中,那么recover()可能出現在panic()之前,也可能出現在panic()之后,出現在panic()之前,因為找不到panic實例而無法生效,出現在panic()之后,代碼沒有機會執行,所以recover()必須存在于defer函數中才會生效。
通過上面的分析,從代碼層面我們理解了為什么recover()函數必須被defer直接調用才會生效。但為什么要有這樣的設計呢?
筆者也沒有找到官方關于此設計的資料,不過筆者認為此設計非常合理。
考慮下面的代碼:
func foo() { defer func() { thirdPartPkg.Clean() // 調用第三方包清理資源 }() if err != nil { // 條件不滿足觸發panic panic(xxx) } }
有時我們會在代碼里顯式地觸發panic,同時往往還會在defer函數里調用第三方包清理資源,如果第三方包也使用了recover(),那么我們觸發的panic將會被攔截,而且這種攔截可能是非預期的,并不我們期望的結果。
“Go編程中recover源碼是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注創新互聯網站,小編將為大家輸出更多高質量的實用文章!
分享文章:Go編程中recover源碼是什么
本文網址:http://m.newbst.com/article14/jpjjde.html
成都網站建設公司_創新互聯,為您提供網站營銷、企業建站、虛擬主機、品牌網站建設、小程序開發、App設計
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯