乐趣区

go-panic-recover源码解析

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, 否则会退出整个程序。

退出移动版