go panic recover源码解析

bunnyhuangw · · 562 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

panicrecover是golang的两个内置函数。
当函数F调用panic,函数F会停止运行,F包裹着的defer函数会全部正常运行,然后返回调用F的函数。如果没有recover,F对于调用方,就像panic。Process继续执行堆栈,直到发生panic的goroutine所有方法返回。panic可以被runtime errors,或者直接调用 panic()函数触发

recover只在defer函数中有效
这一部分内容来自Defer, Panic, and Recover翻译

panic例子

func main() {
    defer println("defer in main")
    go func() {
        defer println("defer in goroutine")
        panic("panic in goroutine")
    }()

}
//输出panic test, defer,程序crash
//main函数里面的defer不会执行,在发生panic时只会执行当前协程中的defer函数,从后续的源码讲解中也可以看到

panic,recover例子

func main() {
    defer println("in main")
    go func() {
        defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
        panic("panic test")
        }()
    }()

}
//输出panic test,Recovered in f, in main
//程序正常

panic的源码是在go源码 runtime/panic.go文件中,这篇讲解主要从panic和recover函数的源码讲解panic是怎么运行

Panic数据结构

type _panic struct {
    argp      unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
    //panic的参数
    arg       interface{}    // argument to panic
    //指向更早的panic
    link      *_panic        // link to earlier panic
    //是否被recover的标识
    recovered bool           // whether this panic is over
    //panic是否被强制终止
    aborted   bool           // the panic was aborted
}  

panic的实现主要是两个函数:panic, recover, 他们分别对应两个实现:gopanic、gorecover,都是在runtime/panic.go文件中实现

gopanic函数

func gopanic(e interface{}) {
    gp := getg()

    var p _panic
    p.arg = e
    p.link = gp._panic //p指向更早的panic
    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

    atomic.Xadd(&runningPanicDefers, 1)
    //遍历defer链表
    for {
        d := gp._defer
        if d == nil {
            break
        }

        // 如果defer已经启动,跳过
        if d.started {
            gp._defer = d.link
            freedefer(d)  //释放defer
            continue
        }

        // 标识defer已经启动
        d.started = true

        // 记录是当前Panic运行这个defer。如果在defer运行期间,有新的Panic,将会标记这个Panic abort=true(强制终止)
        d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

        p.argp = unsafe.Pointer(getargp(0))
        // 调用 defer
        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
        d.fn = nil
        gp._defer = d.link //遍历到下一个defer
        pc := d.pc
        sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
        freedefer(d)
        //已经有recover被调用
        if p.recovered {
            //调用recovery函数
            mcall(recovery)
            throw("recovery failed") // mcall should not return
        }
    }
    //defer遍历完,终止程序
    fatalpanic(gp._panic) // should not return
    *(*int)(nil) = 0      // not reached
}

//panic没有被recover,会运行fatalpanic
func fatalpanic(msgs *_panic) {

    systemstack(func() {
        if startpanic_m() && msgs != nil {
            //打印panic messages
            printpanics(msgs)
        }
        //打印panic messages
        docrash = dopanic_m(gp, pc, sp)
    })

    //终止整个程序,所以需要注意:如果goroutine的Panic没有 recover,会终止整个程序
    systemstack(func() {
        exit(2)
    })

    *(*int)(nil) = 0 // not reached
}

gorecover函数

//defer有recover时,调用;置p的recovered标识位为true
func gorecover(argp uintptr) interface{} {
    // 在panic期间,作为defer的一部分被运行
    gp := getg()
    p := gp._panic
    if p != nil && !p.recovered && argp ==  uintptr(p.argp) {
        p.recovered = true
        return p.arg
    }
    return nil
}

recovery函数

//安排defer函数的调用者正常返回
func recovery(gp *g) {
    //跳转到deferreturn
    gogo(&gp.sched)
}

总结:当发生panic时,会遍历G的defer链表,如发现defer函数包含recover, 则会运行recovery函数,recovery会跳转到deferreturn,否则会退出整个程序。


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:bunnyhuangw

查看原文:go panic recover源码解析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

562 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传