<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 3.5 恐慌與恢復內建函數 panic 能中斷一個程序的執行,同時也能在一定情況下進行恢復。本節我們就來看一看 panic 和 recover 這對關鍵字 的實現機制。根據我們對 Go 的實踐,可以預見的是,他們的實現跟調度器和 defer 關鍵字也緊密相關。 最好的方式當然是了解編譯器究竟做了什么事情: ``` package main func main() { defer func() { recover() }() panic(nil) } ``` 其匯編的形式為: ``` TEXT main.main(SB) /Users/changkun/dev/go-under-the-hood/demo/7-lang/panic/main.go (...) main.go:7 0x104e05b 0f57c0 XORPS X0, X0 main.go:7 0x104e05e 0f110424 MOVUPS X0, 0(SP) main.go:7 0x104e062 e8d935fdff CALL runtime.gopanic(SB) main.go:7 0x104e067 0f0b UD2 (...) ``` 可以看到 panic 這個關鍵詞本質上只是一個`runtime.gopanic`調用。 而與之對應的`recover`則: ``` TEXT main.main.func1(SB) /Users/changkun/dev/go-under-the-hood/demo/7-lang/panic/main.go (...) main.go:5 0x104e09d 488d442428 LEAQ 0x28(SP), AX main.go:5 0x104e0a2 48890424 MOVQ AX, 0(SP) main.go:5 0x104e0a6 e8153bfdff CALL runtime.gorecover(SB) (...) ``` 其實也只是一個`runtime.gorecover`調用。 ## 9.3.1`gopanic`和`gorecover` 正如前面所探究得來,panic 關鍵字不過是一個`gopanic`調用,接受一個參數。 在處理 panic 期間,會先判斷當前 panic 的類型,確定 panic 是否可恢復。 ``` // 預先聲明的函數 panic 的實現 func gopanic(e interface{}) { gp := getg() // 判斷在系統棧上還是在用戶棧上 // 如果執行在系統或信號棧時,getg() 會返回當前 m 的 g0 或 gsignal // 因此可以通過 gp.m.curg == gp 來判斷所在棧 // 系統棧上的 panic 無法恢復 if gp.m.curg != gp { print("panic: ") // 打印 printany(e) // 打印 print("\n") // 繼續打印,下同 throw("panic on system stack") } // 如果正在進行 malloc 時發生 panic 也無法恢復 if gp.m.mallocing != 0 { print("panic: ") printany(e) print("\n") throw("panic during malloc") } // 在禁止搶占時發生 panic 也無法恢復 if gp.m.preemptoff != "" { print("panic: ") printany(e) print("\n") print("preempt off reason: ") print(gp.m.preemptoff) print("\n") throw("panic during preemptoff") } // 在 g 鎖在 m 上時發生 panic 也無法恢復 if gp.m.locks != 0 { print("panic: ") printany(e) print("\n") throw("panic holding locks") } ... } ``` 其他情況,panic 可以從運行時進行恢復,這時候會創建一個`_panic`實例。`_panic`類型 定義了一個`_panic`鏈表: ``` // _panic 保存了一個活躍的 panic // // 這個標記了 go:notinheap 因為 _panic 的值必須位于棧上 // // argp 和 link 字段為棧指針,但在棧增長時不需要特殊處理:因為他們是指針類型且 // _panic 值只位于棧上,正常的棧指針調整會處理他們。 // //go:notinheap type _panic struct { argp unsafe.Pointer // panic 期間 defer 調用參數的指針; 無法移動 - liblink 已知 arg interface{} // panic 的參數 link *_panic // link 鏈接到更早的 panic recovered bool // 表明 panic 是否結束 aborted bool // 表明 panic 是否忽略 } ``` 在創建過程中,panic 保存了對應的消息,并指向了保存在 goroutine 鏈表中先前的 panic 鏈表: ``` var p _panic p.arg = e p.link = gp._panic gp._panic = (*_panic)(noescape(unsafe.Pointer(&amp;p))) atomic.Xadd(&amp;runningPanicDefers, 1) ``` 接下來開始逐一調用當前 goroutine 的 defer 方法, 檢查用戶態代碼是否需要對 panic 進行恢復: ``` for { // 開始逐個取當前 goroutine 的 defer 調用 d := gp._defer // 如果沒有 defer 調用,則跳出循環 if d == nil { break } // 如果 defer 是由早期的 panic 或 Goexit 開始的(并且,因為我們回到這里,這引發了新的 panic), // 則將 defer 帶離鏈表。更早的 panic 或 Goexit 將無法繼續運行。 if d.started { if d._panic != nil { d._panic.aborted = true } d._panic = nil d.fn = nil gp._defer = d.link freedefer(d) continue } // 如果棧增長或者垃圾回收在 reflectcall 開始執行 d.fn 前發生 // 標記 defer 已經開始執行,但仍將其保存在列表中,從而 traceback 可以找到并更新這個 defer 的參數幀 d.started = true // 記錄正在運行 defer 的 panic。如果在 defer 調用期間出現新的 panic,該 panic 將在列表中 // 找到 d 并標記 d._panic(該 panic)中止。 d._panic = (*_panic)(noescape(unsafe.Pointer(&amp;p))) p.argp = unsafe.Pointer(getargp(0)) reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) p.argp = nil // reflectcall 不會 panic. 移出 d. if gp._defer != d { throw("bad defer entry in panic") } d._panic = nil d.fn = nil gp._defer = d.link pc := d.pc sp := unsafe.Pointer(d.sp) // 必須是指針,以便在棧復制期間進行調整 freedefer(d) if p.recovered { atomic.Xadd(&amp;runningPanicDefers, -1) gp._panic = p.link // 忽略的 panic 會被標記,但仍然保留在 g.panic 列表中 // 這里將它們移出列表 for gp._panic != nil &amp;&amp; gp._panic.aborted { gp._panic = gp._panic.link } if gp._panic == nil { // 必須由 signal 完成 gp.sig = 0 } // 傳遞關于恢復幀的信息 gp.sigcode0 = uintptr(sp) gp.sigcode1 = pc // 調用 recover,并重新進入調度循環,不再返回 mcall(recovery) // 如果無法重新進入調度循環,則無法恢復錯誤 throw("recovery failed") } } ``` 這個循環說明了很多問題。首先,當 panic 發生時,如果錯誤是可恢復的錯誤,那么 會逐一遍歷該 goroutine 對應 defer 鏈表中的 defer 函數鏈表,直到 defer 遍歷完畢、 或者再次進入調度循環(recover 的 mcall 調用) 后才會停止。 defer 并非簡單的遍歷,每個在 panic 和 recover 之間的 defer 都會在這里通過`reflectcall`執行。 ``` // reflectcall 使用 arg 指向的 n 個參數字節的副本調用 fn。 // fn 返回后,reflectcall 在返回之前將 n-retoffset 結果字節復制回 arg+retoffset。 // 如果重新復制結果字節,則調用者應將參數幀類型作為 argtype 傳遞,以便該調用可以在復制期間執行適當的寫障礙。 // reflect 包傳遞幀類型。在 runtime 包中,只有一個調用將結果復制回來,即 cgocallbackg1, // 并且它不傳遞幀類型,這意味著沒有調用寫障礙。參見該調用的頁面了解相關理由。 // // 包 reflect 通過 linkname 訪問此符號 func reflectcall(argtype *_type, fn, arg unsafe.Pointer, argsize uint32, retoffset uint32) ``` 如果某個包含了 recover 的調用(即 gorecover 調用)被執行,這時`_panic`實例`p.recovered`會被標記為`true`: ``` // 執行預先聲明的函數 recover。 // 不允許分段棧,因為它需要可靠地找到其調用者的棧段。 // // TODO(rsc): Once we commit to CopyStackAlways, // this doesn't need to be nosplit. //go:nosplit func gorecover(argp uintptr) interface{} { // 必須在 panic 期間作為 defer 調用的一部分在函數中運行。 // 必須從調用的最頂層函數( defer 語句中使用的函數)調用。 // p.argp 是最頂層 defer 函數調用的參數指針。 // 比較調用方報告的 argp,如果匹配,則調用者可以恢復。 gp := getg() p := gp._panic if p != nil &amp;&amp; !p.recovered &amp;&amp; argp == uintptr(p.argp) { p.recovered = true return p.arg } return nil } ``` 同時`recover()`這個函數還會返回 panic 的保存相關信息`p.arg`。 恢復的原則取決于`gorecover`這個方法調用方報告的 argp 是否與`p.argp`相同,僅當相同才可恢復。 當`reflectcall`執行完畢后,這時如果一個 panic 是可恢復的,`p.recovered`已經被標記為`true`, 從而會通過`mcall`的方式來執行`recovery`函數來重新進入調度循環: ``` // 在發生 panic 后 defer 函數調用 recover 后展開棧。然后安排繼續運行, // 就像 defer 函數的調用方正常返回一樣。 func recovery(gp *g) { // 傳遞到 G 結構的 defer 信息 sp := gp.sigcode0 pc := gp.sigcode1 ... // 使 deferproc 為此 d 返回 // 這時候返回 1。調用函數將跳轉到標準的返回尾聲 gp.sched.sp = sp gp.sched.pc = pc gp.sched.lr = 0 gp.sched.ret = 1 gogo(&amp;gp.sched) } ``` 當然如果所有的 defer 都沒有指明顯式的 recover,那么這時候則直接在運行時拋出 panic 信息: ``` // 消耗完所有的 defer 調用,保守地進行 panic // 因為在凍結之后調用任意用戶代碼是不安全的,所以我們調用 preprintpanics 來調用 // 所有必要的 Error 和 String 方法來在 startpanic 之前準備 panic 字符串。 preprintpanics(gp._panic) fatalpanic(gp._panic) // 不應該返回 *(*int)(nil) = 0 // 無法觸及 } ``` 從而完成`gopanic`的調用。 至于`preprintpanics`和`fatalpanic`無非是一些錯誤輸出,不再贅述: ``` // 在停止前調用所有的 Error 和 String 方法 func preprintpanics(p *_panic) { defer func() { if recover() != nil { throw("panic while printing panic value") } }() for p != nil { switch v := p.arg.(type) { case error: p.arg = v.Error() case stringer: p.arg = v.String() } p = p.link } } // fatalpanic 實現了不可恢復的 panic。類似于 fatalthrow, // 要求如果 msgs != nil,則 fatalpanic 仍然能夠打印 panic 的消息并在 main 在退出時候減少 runningPanicDefers。 // //go:nosplit func fatalpanic(msgs *_panic) { pc := getcallerpc() sp := getcallersp() gp := getg() var docrash bool // 切換到系統棧來避免棧增長,如果運行時狀態較差則可能導致更糟糕的事情 systemstack(func() { if startpanic_m() &amp;&amp; msgs != nil { // 有 panic 消息和 startpanic_m 則可以嘗試打印它們 // startpanic_m 設置 panic 會從阻止 main 的退出, // 因此現在可以開始減少 runningPanicDefers 了 atomic.Xadd(&amp;runningPanicDefers, -1) printpanics(msgs) } docrash = dopanic_m(gp, pc, sp) }) if docrash {= // 通過在上述 systemstack 調用之外崩潰,調試器在生成回溯時不會混淆。 // 函數崩潰標記為 nosplit 以避免堆棧增長。 crash() } // 從系統棧退出 systemstack(func() { exit(2) }) *(*int)(nil) = 0 // 不可達 } ``` ## 小結 從 panic 和 recover 這對關鍵字的實現上可以看出,可恢復的 panic 必須要 recover 的配合。 而且,這個 recover 必須位于同一 goroutine 的直接調用鏈上,否則無法對 panic 進行恢復。 例如,如果 ``` func A () { B() C() } func B() { defer func () { recover() // 無法恢復 panic("C") }() println("B") } func C() { panic("C") } ``` 又例如 A 調用了 B 而 B 又調用了 C,那么 C 發生 panic 時,如果 A 要求了 recover 則仍然可以恢復。 ``` func A () { defer func () { recover() // 可以恢復 panic("C") }() B() } func B() { C() } func C() { panic("C") } ``` 當一個 panic 被恢復后,調度并因此中斷,會重新進入調度循環,進而繼續執行 recover 后面的代碼, 包括比 recover 更早的 defer(因為已經執行過得 defer 已經被釋放, 而尚未執行的 defer 仍在 goroutine 的 defer 鏈表中),或者 recover 所在函數的調用方。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看