日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

recover.panic.defer.2021.03.03

 印度阿三17 2021-03-09

Defer, Panic, and Recover

在 Go 語言中,recover 和 panic 的關系是什么?

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

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

for{}
}

針對這種情況,我們引入 recover 方法。這里故意寫了一段錯誤的代碼,代碼如下,運行的結果會怎么樣呢?能 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{}
}

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

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 類型的結構體。panic 的時候,我們指定的入?yún)⑹且粋€ inner 結構體變量,inner 的 Msg 成員值為 Thank。然后,我們對 recover 的返回做斷言處理(因為返回類型為 interface),直接斷言它為 inner 值類型。

工作中,我們經(jīng)常遇到的切片下標越界,go 在處理到這種類型的 panic 時,默認傳遞的就是 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 嵌套

當程序 panic 之后,調用 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 進行捕獲。從代碼的輸出結果中,我們可以發(fā)現(xiàn),代碼還是 panic 了。

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

我們稍作調整,在 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 的類型結構:

arg 作為 panic 是的入?yún)ⅲ瑢覀冋{用 panic 函數(shù)是的入?yún)?。在后續(xù) recover 的時候會返回這個參數(shù)。

link 作為一個 _panic 類型指針,通過這個類型,可以說明:在 Goroutine 內部 _panic 是按照鏈表的結構存儲的。在一個 goroutine 內,可能會出現(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 方法體代碼比較長,我們直接在注釋中對它進行標注和分析

// 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 指針指向當前 goroutine 的 _panic 列表
    // 說白了就是一個鏈表操作,將當前 panic 插入到當前 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 一樣按照鏈表結構進行存儲的。
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() 方法返回當前的 goroutine,之后是獲取當前 Go 的 panic 信息。緊接著 if 判斷,如果條件符合的話,將這個 panic 對象的 recovered 屬性設置為 true,也就是標記為被處理了,并返回的是這個 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ù)捕獲的是祖父一級調用函數(shù)棧的異常。必須要和有異常的棧幀只隔一個棧幀,recover函數(shù)才能正捕獲異常。

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

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多