自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go 語言中 panic 和 recover 搭配使用

開發(fā) 前端
當我們在同一個協(xié)程中出現(xiàn)了 panic,且在同一個協(xié)程中去使用 defer 來配合 recover 來進行捕獲異常和處理異常,就可以得以實現(xiàn),看到這里,有沒有覺得還是蠻簡單的,不就是去對一個 p.recovered 進行配合處理嗎?

本次主要聊聊 Go 語言中關于 panic 和 recover 搭配使用 ,以及 panic 的基本原理

最近工作中審查代碼的時候發(fā)現(xiàn)一段代碼,類似于如下這樣,將 recover 放到一個子協(xié)程里面,期望去捕獲主協(xié)程的程序異常

圖片圖片

看到此處,是否會想這段代碼在項目中是想當然寫出來的吧,然而平日中,大多問題是出現(xiàn)在認知偏差上,那么本次,我們就來消除一下這個認知偏差

關于 Go 語言中顯示的使用 panic 的地方不多,一般 panic ,基本上會出現(xiàn)在咱們程序出現(xiàn)異常退出的時候

例如訪問了空指針里面的值,則會 panic 報錯無效的內存地址,又例如訪問量數(shù)組中不存在的數(shù)組所索引,或者切片索引,那么會報錯 panic 數(shù)組越界等等

可是碰到這些 panic 的時候,實際上我們并不期望當前的服務直接掛掉,而是期望這個異常能夠被識別,且不影響程序其他部分的模塊運行

正常捕獲異常

在 Go 中可以將 defer 和 recover 進行搭配使用,可以捕獲和處理大部分的異常情況,例如可以這樣

圖片圖片

這里可以看到,recover 捕獲異常和發(fā)生異常的部分是在同一個協(xié)程中,實驗證明是可以正常捕獲并且處理異常

并沒有捕獲到異常

  1. 直接不做顯示的 recover,自然 panic 程序崩潰會如期而至,此處我們顯示的使用 panic 函數(shù)來制造恐慌
func main() {
   log.SetFlags(log.Lshortfile)

   panic("panic coming...")

}

圖片圖片

  1. 不使用 defer 來進行處理
func main() {
   log.SetFlags(log.Lshortfile)
    if err := recover(); err != nil {
     log.Println("recover panic : ", err)
    }
   panic("panic coming...")

}

圖片圖片

自然 recover 函數(shù)是在 panic 調用之前就已經執(zhí)行,此時是還沒有異常需要捕獲和恢復的,待程序運行到 panic 處的時候,實際上并沒有沒有處理程序崩潰的異常

結果,仍然是程序崩潰

  1. 當然,還有文章開頭提到的出現(xiàn) panic 的位置和捕獲和處理程序崩潰異常的位置不在同一個協(xié)程,自然也是沒法捕獲到的,這一點需要注意,其他的語言可能不是這樣,但是 Go 中是這樣的

panic 基本原理

看了上述現(xiàn)象,實際上還是對知識點理解得不夠,使用的時候想當然了,就像使用 defer 一樣,如果對他不夠了解的話,使用的時候,確實會出現(xiàn)一些奇奇怪怪的現(xiàn)象,對于 defer 的使用可以查看文末的文章地址

  1. panic 函數(shù)和 recover 函數(shù),Go 源碼builtin\builtin.go中可以看到注釋

圖片圖片

注釋中有說關于 panic 和 recover 的使用是作用于當前協(xié)程的,因此我們使用的時候,如果跨協(xié)程教程使用,自然不會達到我們期望的效果

  1. 繼續(xù)查看關于 panic 的源碼,實際上是一個結構,放到 defer 結構里面的一個指針,源碼位置:runtime\runtime2.go

圖片圖片

_panic 的結構如下:

type _panic struct {
   argp      unsafe.Pointer
   arg       interface{}
   link      *_panic
   pc        uintptr
   sp        unsafe.Pointer
   recovered bool
   aborted   bool
   goexit    bool
}

上述兩個結構表達的意思是,程序中出現(xiàn) panic 的時候,實際上都會創(chuàng)建一個 _panic 結構,這個 _panic 結構里面存儲了當前程序崩潰的一些必要信息,如下:

  1. argp

是一個 unsafe.Pointer 類型的成員,指向 defer 調用參數(shù)的指針

  1. arg

出現(xiàn) panic 的原因,如果我們顯示調用 panic,那么就是我們填入 panic 函數(shù)中的參數(shù),例如上述的 panic coming ...

  1. link

是一個指針,指向上一個,最近的一個 _panic 結構的地址,實際上此處就可以看到這個指針對應的是一個鏈表,一個又多個 _panic 結構組成的鏈表

圖片圖片

  1. recovered

panic 是否已經處理完畢,即當前的這個 panic 是否是已經被 recover 了

  1. aborted

表示當前的 panic 是否被中止

  1. 對于 pc 和 sp 自然就是我們熟知的 pc 通用寄存器,在匯編中是指向當前運行指令的下一條指令,sp 則是棧指針 stack pointer,用于入棧和出棧的

我們知道運行函數(shù)的時候需要入棧,運行完畢之后需要出棧

源碼中的 runtime.gopanic

那么我們繼續(xù)來閱讀源碼,上述看到 sp 和 pc ,那么我們就簡單寫一個 panic 的代碼來看看匯編到底是怎么執(zhí)行的,不用擔心看不懂,我們只需要看關鍵詞就行

還是上面的程序

圖片圖片

程序運行的時候可以執(zhí)行 go tool compile -S main.go

可以看到匯編代碼,可能其他的看不懂,但是我們可以看到如下關鍵詞

圖片圖片

  • log.(*Logger).SetFlags(SB) 即是執(zhí)行到我們調用 log 去設置參數(shù)
  • 程序走到 panic 函數(shù)的時候,實際上是執(zhí)行了 runtime.gopanic 函數(shù),我們一起看看源碼

圖片圖片

代碼中可以看到 p.recovered 邏輯下的關于 recover 的邏輯被刪除掉了,在文章的后面會繼續(xù)說到,當前我們先關注 panic 的事項

runtime.gopanic 程序的邏輯大體是這樣的

  1. 獲取當前 協(xié)程 的指針
  2. 初始化一個 _panic 結構 p,并將當前協(xié)程上對應的數(shù)據賦值給到 p 上,且將 當前協(xié)程 _panic 掛到 link 上
  3. 進入循環(huán)后,拿到當前協(xié)程的 _defer 數(shù)據
  4. 查看 _defer 指針數(shù)據 中是否有 defer 調用,如果有則執(zhí)行
  5. 處理完基本邏輯之后,打印 panic 信息,例如我們 demo 中的 panic coming ... 信息
  6. 最終退出程序

Xdm 可以看上圖,自己捋一捋邏輯就清晰了

接著,我們來看

fatalpanic

圖片圖片

通過 runtime.gopanic 我們可以看到 fatalpanic 函數(shù)基本上就是做一個收尾工作了,如果上述程序處理完畢之后, fatalpanic 校驗到 panic 是需要 recover 的,那么就打印 [recovered]

打印的這個信息是由 上圖中 printpanics 完成的

圖片圖片

這下知道 panic 是如何去執(zhí)行的了,那么對于現(xiàn)在來研究 recover 是如何落實的

recover

還是同一個例子,咱們將 defer 部分的代碼注打開,來繼續(xù)看看效果

func main() {
   log.SetFlags(log.Lshortfile)

   defer func() {
      if err := recover(); err != nil {
         log.Println("recover panic : ", err)
      }
   }()

   panic("panic coming...")

}

自然效果是我們期望的,捕獲到了異常,且處理了

圖片圖片

繼續(xù)打印匯編來查看一下關鍵詞,是否有我們期望的函數(shù)出現(xiàn)

圖片圖片

圖片圖片

此處我們可以看到,實際 Go 中調用了多個函數(shù)

  1. runtime.gorecover
  2. main.main.opendefer
  3. log.(*Logger).SetFlags
  4. runtime.gopanic
  5. runtime.deferreturn

自然明眼人都看的出現(xiàn),關鍵的函數(shù)實現(xiàn)自然是 runtime.gorecover ,那么我們來一探究竟

runtime.gorecover

圖片圖片

查看源碼我們可以知道, runtime.gorecover 實際上就是根據當前協(xié)程的 _panic 結構數(shù)據來判斷是否需要恢復,如果需要則將 p.recovered = true

自然在這里將當前協(xié)程的數(shù)據修改掉,正是為了后續(xù)執(zhí)行 runtime.gopanic 的時候提供保障, runtime.gopanic 執(zhí)行的時候就會去判斷和處理這個 p.recovered

前文中提到的關于 runtime.gopanic 中 處理 p.recovered 的邏輯是這樣的

圖片圖片

圖片圖片

  1. 如上可以看到 runtime.gorecover 去對 p.recovered 設置是否恢復
  2. runtime.gopanic 中校驗 p.recovered 已處理,則執(zhí)行 recovery 函數(shù)
  3. recovery 函數(shù)中去處理對應的寄存器的值去維護上下文
  4. 最后我們可以看到最終調用 gogo 函數(shù)跳回原來調用的位置

因此,當我們在同一個協(xié)程中出現(xiàn)了 panic,且在同一個協(xié)程中去使用 defer 來配合 recover 來進行捕獲異常和處理異常,就可以得以實現(xiàn),看到這里,有沒有覺得還是蠻簡單的,不就是去對一個 p.recovered 進行配合處理嗎

自然,表面上是這樣,其中對于寄存器的各種數(shù)據處理涉及的內容還是不少的,不過這不在我們今天聊的范疇中了

總結

至此,相信你已經知道了這些

  1. 為什么 panic 和 defer ,recover 配合使用的時候要在同一個協(xié)程中了吧
  2. 相信你還知道了 panic 和 recover 的處理流程
責任編輯:武曉燕 來源: 阿兵云原生
相關推薦

2013-06-25 09:52:32

GoGo語言Go編程

2025-02-06 13:19:31

RustPin系統(tǒng)

2024-04-01 00:02:56

Go語言代碼

2024-05-10 08:36:40

Go語言對象

2023-11-21 15:46:13

Go內存泄漏

2023-12-30 18:35:37

Go識別應用程序

2012-06-15 09:56:40

2023-05-06 09:36:38

RecoverPanic

2014-04-09 09:32:24

Go并發(fā)

2024-01-07 23:11:16

defer?Go語言

2025-03-31 08:57:25

Go程序性能

2023-04-09 23:09:59

Go語言函數(shù)

2021-12-31 09:28:46

小字端大字端Go

2023-12-21 07:09:32

Go語言任務

2023-01-12 08:52:50

GoroutinesGo語言

2021-07-15 23:18:48

Go語言并發(fā)

2024-04-07 11:33:02

Go逃逸分析

2021-09-27 23:28:29

Go多協(xié)程并發(fā)

2025-03-31 00:29:44

2023-01-31 08:48:49

Go語言文件
點贊
收藏

51CTO技術棧公眾號