Golang 中 panic 和 defer 的执行顺序

👨‍🏫 go中 defer 和 panic  执行顺序.

本文重点分析的是defer和panic执行顺序之间的关系,而不是为了分析panic.go源码而进行分析源码.

💡    可以先看下下面的Demo .
func main(){
    call()
    fmt.Println("333 Helloworld")
}

func call()  {

    defer func(){
        fmt.Println("11111")
    }()
    defer func(){
        fmt.Println("22222")
    }()
    defer func() {
        if r := recover(); r!= nil {
            fmt.Println("Recover from r : ",r)
        }
    }()
    defer func(){
        fmt.Println("33333")
    }()

    fmt.Println("111 Helloworld")
    panic("Panic 1!")
    panic("Panic 2!")
    fmt.Println("222 Helloworld")
}

输出结果:

111 Helloworld
33333
Recover from r :  Panic 1!
22222
11111
333 Helloworld
💡 为什么?

我们从Panic源码分析,panic源码位于%GOROOT%/src/rumtime/panic.go:425

虽然看的不是很懂对于gopanic,了解下大概流程原理即可。中间删掉了很多对于本文无关的判断信息。

// panic 关键字代码实现. 
func gopanic(e interface{}) {
    gp := getg()    // getg()返回当前协程的 g 结构体指针,g 结构体描述 goroutine
    if gp.m.curg != gp {
        print("panic: ")
        printany(e)
        print("n")
        throw("panic on system stack")
    }

    //  省略无用代码. 
    var p _panic
    p.arg = e
    p.link = gp._panic
    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

    atomic.Xadd(&runningPanicDefers, 1)

    for {

        // 获取当前协程的defer链表.
        // 这里defer链表和在代码定义的顺序是相反的,类似于先进后出的概念. 
        d := gp._defer    
        if d == nil {
            break    // 当前协程的defer都被执行后,defer链表为空,此时退出for循环
        }

        // If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
        // take defer off list. The earlier panic or Goexit will not continue running.
        if d.started {    // 发生panic后,在defer中又遇到panic(),则会进入这个代码块
            if d._panic != nil {
                d._panic.aborted = true
            }
            d._panic = nil
            d.fn = nil
            gp._defer = d.link
            freedefer(d)  // defer 已经被执行过,则释放这个defer,继续for循环。
            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)))

        p.argp = unsafe.Pointer(getargp(0))
        reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))   // 执行当前协程defer链表头的defer
        p.argp = nil

        // reflectcall did not panic. Remove d.
        if gp._defer != d {
            throw("bad defer entry in panic")
        }
        d._panic = nil
        d.fn = nil
        gp._defer = d.link  // 从defer链中移除刚刚执行过的defer

        // 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
        freedefer(d)   // 释放刚刚执行过的defer
        if p.recovered {    // defer()中遇到recover后进入这个代码块
            atomic.Xadd(&runningPanicDefers, -1)

            gp._panic = p.link
            mcall(recovery)   // 跳转到recover()处,继续往下执行
            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)
    startpanic()

    // startpanic set panicking, which will block main from exiting,
    // so now OK to decrement runningPanicDefers.
    atomic.Xadd(&runningPanicDefers, -1)
    printpanics(gp._panic)   // 输出panic信息
    dopanic(0)       // should not return
    *(*int)(nil) = 0 // not reached
}

总结:

大概的流程就是,如果遇见panic关键字的话,那么go执行器就会进入代码gopanic函数中,进入之后会拿到表示当前协程g的指针,然后通过该指针拿到当前协程的defer链表,通过for循环来进行执行defer,如果在defer中又遇见了panic的话,则会释放这个defer,通过continue去执行下一个defer,然后就是一个一个的执行defer了,如果在defer中遇见recover,那么将会通过mcall(recovery)去执行panic.   所以,现在就应该会明白panic和defer执行顺序是为什么了吧?


原文始发于微信公众号(社恐的小马同学):Golang 中 panic 和 defer 的执行顺序

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/269786.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!