成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go 語言中 panic 和 recover 搭配使用

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

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

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

圖片圖片

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

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

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

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

正常捕獲異常

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

圖片圖片

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

并沒有捕獲到異常

  1. 直接不做顯示的 recover,自然 panic 程序崩潰會如期而至,此處我們顯示的使用 panic 函數來制造恐慌
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 函數是在 panic 調用之前就已經執行,此時是還沒有異常需要捕獲和恢復的,待程序運行到 panic 處的時候,實際上并沒有沒有處理程序崩潰的異常

結果,仍然是程序崩潰

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

panic 基本原理

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

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

圖片圖片

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

  1. 繼續查看關于 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
}

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

  1. argp

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

  1. arg

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

  1. link

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

圖片圖片

  1. recovered

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

  1. aborted

表示當前的 panic 是否被中止

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

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

源碼中的 runtime.gopanic

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

還是上面的程序

圖片圖片

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

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

圖片圖片

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

圖片圖片

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

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

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

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

接著,我們來看

fatalpanic

圖片圖片

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

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

圖片圖片

這下知道 panic 是如何去執行的了,那么對于現在來研究 recover 是如何落實的

recover

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

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

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

   panic("panic coming...")

}

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

圖片圖片

繼續打印匯編來查看一下關鍵詞,是否有我們期望的函數出現

圖片圖片

圖片圖片

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

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

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

runtime.gorecover

圖片圖片

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

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

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

圖片圖片

圖片圖片

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

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

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

總結

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

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

2013-06-25 09:52:32

GoGo語言Go編程

2025-02-06 13:19:31

RustPin系統

2025-06-09 01:15:00

2024-04-01 00:02:56

Go語言代碼

2024-05-10 08:36:40

Go語言對象

2012-06-15 09:56:40

2023-12-30 18:35:37

Go識別應用程序

2023-11-21 15:46:13

Go內存泄漏

2023-05-06 09:36:38

RecoverPanic

2024-01-07 23:11:16

defer?Go語言

2014-04-09 09:32:24

Go并發

2023-04-09 23:09:59

Go語言函數

2025-03-31 08:57:25

Go程序性能

2024-04-07 11:33:02

Go逃逸分析

2021-07-15 23:18:48

Go語言并發

2023-12-21 07:09:32

Go語言任務

2023-01-12 08:52:50

GoroutinesGo語言

2021-12-31 09:28:46

小字端大字端Go

2021-09-27 23:28:29

Go多協程并發

2025-03-31 00:29:44

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 一级黄色片在线免费观看 | 精品亚洲91 | 亚洲天堂成人在线视频 | 亚洲乱码一区二区三区在线观看 | 美女毛片| 久久激情视频 | 成人av鲁丝片一区二区小说 | 中文字幕亚洲区一区二 | 国产在线网址 | 亚洲精品在线观 | 精品三级在线观看 | 国产97色 | 中文字幕在线一区 | 日批免费观看 | 免费一级大片 | 久久久久国产一级毛片高清网站 | 亚洲综合色视频在线观看 | 国产精品视频一区二区三区 | 国产亚洲一区二区三区在线 | 日本免费网 | 欧美在线视频一区二区 | 97色在线观看免费视频 | 国产一区二区精品在线观看 | 黄色网址免费在线观看 | 欧美成视频| 91福利影院 | 一级毛片视频 | 中文字幕99 | 日韩国产免费 | 91夜夜夜| 欧美性大战xxxxx久久久 | 日韩第一页 | 亚洲一区二区三区四区在线观看 | 国产精品久久久久一区二区三区 | 一区二区在线观看免费视频 | 91精品国产综合久久精品 | 国产91在线视频 | 蜜桃黄网 | 在线播放一区二区三区 | 国产性色视频 | 欧美一区二区网站 |