乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      recover.panic.defer.2021.03.03

       印度阿三17 2021-03-09

      Defer, Panic, and Recover

      在 Go 語言中,recover 和 panic 的關(guān)系是什么?

      我們先看一個基礎(chǔ)的例子,在 main 方法體中啟動一個協(xié)程,在協(xié)程內(nèi)部主動調(diào)用 panic。程序的執(zhí)行會被中斷了,但有個疑問,為什么在別的協(xié)程里調(diào)用了 panic,要讓 main 協(xié)程也退出呢?

      func main() {
      go func() {
      panic("call panic")
      }()
      
      for{}
      }
      

      針對這種情況,我們引入 recover 方法。這里故意寫了一段錯誤的代碼,代碼如下,運(yùn)行的結(jié)果會怎么樣呢?能 recover 住 panic 嗎?

      程序執(zhí)行還是被中斷了,recover 并沒有起作用。因為 recover 沒有寫在 defer 函數(shù)里。實際上,recover 和 defer 聯(lián)用,并且不跨協(xié)程,才能真正的攔截 panic 事件。

      func main() {
      go func() {
          
          // 追加的代碼
      if r := recover(); r != nil {
      fmt.Println(r)
      }
      
      panic("call panic")
      }()
      
      for{}
      }
      

      正確的寫法如下。這里描述的內(nèi)容在 Go 博客Defer, Panic, and Recover 有詳細(xì)解釋。

      func main() {
      go func() {
      defer func() {
      if r := recover(); r != nil {
      fmt.Println(r)
      }
      }()
      
      panic("call panic")
      }()
      
      fmt.Println("come on")
      }
      
      

      Panic 和 Recover 的聯(lián)系

      在 panic 的過程中, panic 傳入的參數(shù)用來作為 recover 函數(shù)的返回。

      下面的例子中,聲明了一個 inner 類型的結(jié)構(gòu)體。panic 的時候,我們指定的入?yún)⑹且粋€ inner 結(jié)構(gòu)體變量,inner 的 Msg 成員值為 Thank。然后,我們對 recover 的返回做斷言處理(因為返回類型為 interface),直接斷言它為 inner 值類型。

      工作中,我們經(jīng)常遇到的切片下標(biāo)越界,go 在處理到這種類型的 panic 時,默認(rèn)傳遞的就是 runtime 包下的 boundsError(A boundsError represents an indexing or slicing operation gone wrong.)。

      type inner struct {
      Msg string
      }
      
      func main() {
      
      defer func() {
      if r := recover(); r != nil {
      fmt.Print(r.(inner))
      }
      }()
      
      panic(inner{Msg: "Thank"})
      }
      

      panic 嵌套

      當(dāng)程序 panic 之后,調(diào)用 defer 函數(shù)時又觸發(fā)了程序再次 panic。在程序的錯誤棧輸出信息中,三處 panic 的錯誤信息都輸出了。

      我們不使用任何 recover ,查看 panic 的輸出信息。從代碼末尾的注釋中可以發(fā)現(xiàn),三個 panic 都觸發(fā)了,而且輸出中也包含了三個 panic 的信息。

      func main() {
          go func() {
      
              // defer 1
              defer func() {
      
                  // defer 2
                  defer func() {
                      panic("call panic 3")
                  }()
      
                  panic("call panic 2")
              }()
      
              panic("call panic 1")
          }()
      
          for{}
      }
      
      //output:
      //panic: call panic 1
      //        panic: call panic 2
      //        panic: call panic 3
      //
      //goroutine 18 [running]:
      //main.main.func1.1.1()
      //        /Users/fuhui/Desktop/panic/main.go:10  0x39
      

      接下來,我們代碼做 recover 處理,觀察程序的輸出情況。上面的示例中,程序依次觸發(fā)了 panic 1、2、3?,F(xiàn)在我們修改代碼,對 panic 3 做捕獲處理,程序還會繼續(xù) panic 嗎?

      我們在代碼中又嵌套追加了第三個 defer,對 panic 3 進(jìn)行捕獲。從代碼的輸出結(jié)果中,我們可以發(fā)現(xiàn),代碼還是 panic 了。

      雖然我們還不了解具體的實現(xiàn),但至少我們可以明白:Go 程序中的 panic 都需要被 recover 處理掉,才不會觸發(fā)程序終止。如果只處理鏈路中的最后一個,程序還是會異常終止。

      我們稍作調(diào)整,在 defer 3 中再寫三個 recover 語句可行嗎?這樣也是不可行的,defer、panic、recover 需要是一體的,大家可以自行驗證。

      func main() {
          go func() {
      
              // defer 1
              defer func() {
      
                  // defer 2
                  defer func() {
      
                      // defer 3
                      defer func() {
                          if r := recover(); r != nil{
                              fmt.Println("recover", r)
                          }
                      }()
      
                      panic("call panic 3")
                  }()
      
                  panic("call panic 2")
              }()
      
              panic("call panic 1")
          }()
      
          for{}
      }
      
      //output:
      //recover panic 3
      //panic: call panic 1
      //        panic: call panic 2
      //
      //goroutine 18 [running]:
      
      

      源碼

      Go 源碼版本

      確定 Go 源碼的版本

      ?  server go version
      go version go1.15.1 darwin/amd64
      

      gopanic

      我們來看 panic 的類型結(jié)構(gòu):

      arg 作為 panic 是的入?yún)ⅲ瑢?yīng)我們調(diào)用 panic 函數(shù)是的入?yún)?。在后續(xù) recover 的時候會返回這個參數(shù)。

      link 作為一個 _panic 類型指針,通過這個類型,可以說明:在 Goroutine 內(nèi)部 _panic 是按照鏈表的結(jié)構(gòu)存儲的。在一個 goroutine 內(nèi),可能會出現(xiàn)多個 panic,但這些 panic 信息都會被存儲。

      // A _panic holds information about an active panic.
      //
      // This is marked go:notinheap because _panic values must only ever
      // live on the stack.
      //
      // The argp and link fields are stack pointers, but don't need special
      // handling during stack growth: because they are pointer-typed and
      // _panic values only live on the stack, regular stack pointer
      // adjustment takes care of them.
      //
      //go:notinheap
      type _panic struct {
      argp      unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
      arg       interface{}    // argument to panic
      link      *_panic        // link to earlier panic
      pc        uintptr        // where to return to in runtime if this panic is bypassed
      sp        unsafe.Pointer // where to return to in runtime if this panic is bypassed
      recovered bool           // whether this panic is over
      aborted   bool           // the panic was aborted
      goexit    bool
      }
      
      

      gopanic 方法體代碼比較長,我們直接在注釋中對它進(jìn)行標(biāo)注和分析

      // The implementation of the predeclared function panic.
      func gopanic(e interface{}) {
      gp := getg()
      if gp.m.curg != gp {
      print("panic: ")
      printany(e)
      print("\n")
      throw("panic on system stack")
      }
      
      if gp.m.mallocing != 0 {
      print("panic: ")
      printany(e)
      print("\n")
      throw("panic during malloc")
      }
      if gp.m.preemptoff != "" {
      print("panic: ")
      printany(e)
      print("\n")
      print("preempt off reason: ")
      print(gp.m.preemptoff)
      print("\n")
      throw("panic during preemptoff")
      }
      if gp.m.locks != 0 {
      print("panic: ")
      printany(e)
      print("\n")
      throw("panic holding locks")
      }
          
          // 創(chuàng)建了這個 panic 對象,將這個 panic 對象的 link 指針指向當(dāng)前 goroutine 的 _panic 列表
          // 說白了就是一個鏈表操作,將當(dāng)前 panic 插入到當(dāng)前 goroutine panic 鏈表的首位置
      var p _panic
      p.arg = e
      p.link = gp._panic
      gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
      
      atomic.Xadd(&runningPanicDefers, 1)
      
      // By calculating getcallerpc/getcallersp here, we avoid scanning the
      // gopanic frame (stack scanning is slow...)
      addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
      
      for {
          
          // 循環(huán)獲取 gp 的 defer,這里不展開,但 _defer 也是跟 _panic 一樣按照鏈表結(jié)構(gòu)進(jìn)行存儲的。
      d := gp._defer
      if d == nil {
      break
      }
      
      // If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
      // take defer off list. An earlier panic will not continue running, but we will make sure below that an
      // earlier Goexit does continue running.
      if d.started {
      if d._panic != nil {
      d._panic.aborted = true
      }
      d._panic = nil
      if !d.openDefer {
      // For open-coded defers, we need to process the
      // defer again, in case there are any other defers
      // to call in the frame (not including the defer
      // call that caused the panic).
      d.fn = nil
      gp._defer = d.link
      freedefer(d)
      continue
      }
      }
      
      // Mark defer as started, but keep on list, so that traceback
      // can find and update the defer's argument frame if stack growth
      // or a garbage collection happens before reflectcall starts executing d.fn.
      d.started = true
      
      // Record the panic that is running the defer.
      // If there is a new panic during the deferred call, that panic
      // will find d in the list and will mark d._panic (this panic) aborted.
      d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
      
      done := true
      if d.openDefer {
      done = runOpenDeferFrame(gp, d)
      if done && !d._panic.recovered {
      addOneOpenDeferFrame(gp, 0, nil)
      }
      } else {
      p.argp = unsafe.Pointer(getargp(0))
      reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
      }
      p.argp = nil
      
      // reflectcall did not panic. Remove d.
      if gp._defer != d {
      throw("bad defer entry in panic")
      }
      d._panic = nil
      
      // trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
      //GC()
      
      pc := d.pc
      sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
      if done {
      d.fn = nil
      gp._defer = d.link
      freedefer(d)
      }
      if p.recovered {
      gp._panic = p.link
      if gp._panic != nil && gp._panic.goexit && gp._panic.aborted {
      // A normal recover would bypass/abort the Goexit.  Instead,
      // we return to the processing loop of the Goexit.
      gp.sigcode0 = uintptr(gp._panic.sp)
      gp.sigcode1 = uintptr(gp._panic.pc)
      mcall(recovery)
      throw("bypassed recovery failed") // mcall should not return
      }
      atomic.Xadd(&runningPanicDefers, -1)
      
      if done {
      // Remove any remaining non-started, open-coded
      // defer entries after a recover, since the
      // corresponding defers will be executed normally
      // (inline). Any such entry will become stale once
      // we run the corresponding defers inline and exit
      // the associated stack frame.
      d := gp._defer
      var prev *_defer
      for d != nil {
      if d.openDefer {
      if d.started {
      // This defer is started but we
      // are in the middle of a
      // defer-panic-recover inside of
      // it, so don't remove it or any
      // further defer entries
      break
      }
      if prev == nil {
      gp._defer = d.link
      } else {
      prev.link = d.link
      }
      newd := d.link
      freedefer(d)
      d = newd
      } else {
      prev = d
      d = d.link
      }
      }
      }
      
      gp._panic = p.link
      // Aborted panics are marked but remain on the g.panic list.
      // Remove them from the list.
      for gp._panic != nil && gp._panic.aborted {
      gp._panic = gp._panic.link
      }
      if gp._panic == nil { // must be done with signal
      gp.sig = 0
      }
      // Pass information about recovering frame to recovery.
      gp.sigcode0 = uintptr(sp)
      gp.sigcode1 = pc
      mcall(recovery)
      throw("recovery failed") // mcall should not return
      }
      }
      
      // ran out of deferred calls - old-school panic now
      // Because it is unsafe to call arbitrary user code after freezing
      // the world, we call preprintpanics to invoke all necessary Error
      // and String methods to prepare the panic strings before startpanic.
      preprintpanics(gp._panic)
      
      fatalpanic(gp._panic) // should not return
      *(*int)(nil) = 0      // not reached
      }
      

      gorecover

      源碼中的 getg() 方法返回當(dāng)前的 goroutine,之后是獲取當(dāng)前 Go 的 panic 信息。緊接著 if 判斷,如果條件符合的話,將這個 panic 對象的 recovered 屬性設(shè)置為 true,也就是標(biāo)記為被處理了,并返回的是這個 panic 的參數(shù)。如果 if 條件不滿足的話,表示沒有 panic 對象被捕獲,返回空。

      // The implementation of the predeclared function recover.
      // Cannot split the stack because it needs to reliably
      // find the stack segment of its caller.
      //
      // TODO(rsc): Once we commit to CopyStackAlways,
      // this doesn't need to be nosplit.
      //go:nosplit
      func gorecover(argp uintptr) interface{} {
      // Must be in a function running as part of a deferred call during the panic.
      // Must be called from the topmost function of the call
      // (the function used in the defer statement).
      // p.argp is the argument pointer of that topmost deferred function call.
      // Compare against argp reported by caller.
      // If they match, the caller is the one who can recover.
      gp := getg()
      p := gp._panic
      if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
      p.recovered = true
      return p.arg
      }
      return nil
      }
      

      注:recover函數(shù)捕獲的是祖父一級調(diào)用函數(shù)棧的異常。必須要和有異常的棧幀只隔一個棧幀,recover函數(shù)才能正捕獲異常。

      來源:https://www./content-4-883851.html

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多