【Golang】panic和recover底层逻辑实现|Go主题月

1,318 阅读2分钟

底层逻辑

在每个goroutine也有一个指针指向_panic链表表头,然后每增加一个panic就会在链表头部加入一个_panic结构体。当所有的defer执行完后,_panic链表就会从尾部开始打印panic信息了,也就是说先发生的panic先打印信息。panic2

_panic结构体

go源码的runtime/runtime2.go中有_panic的结构体信息

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
}

panic3

结构体中的 pcspgoexit 三个字段都是为了修复 runtime.Goexit 带来的问题引入的。runtime.Goexit 能够只结束调用该函数的 Goroutine 而不影响其他的 Goroutine,但是该函数会被 defer 中的 panicrecover 取消,引入这三个字段就是为了保证该函数的一定会生效。

嵌套panic

接下来用个嵌套的panic例子来强化理解一下上面讲的

package main

func main() {
	defer func() {
		defer func() {
			panic("双重嵌套:panic")
		}()
		panic("panic1")
	}()
	defer func() {
		panic("panic2")
	}()
	panic("main-panic")
}

输出:

panic: main-panic
        panic: panic2
        panic: panic1
        panic: 双重嵌套:panic

goroutine 1 [running]:
main.main.func1.1()
        /home/zheng/STUDY/GoWork/demo/main.go:6 +0x39
panic(0x466460, 0x48a2b8)
        /usr/local/go/src/runtime/panic.go:965 +0x1b9
main.main.func1()
        /home/zheng/STUDY/GoWork/demo/main.go:8 +0x5b
panic(0x466460, 0x48a2c8)
        /usr/local/go/src/runtime/panic.go:965 +0x1b9
main.main.func2()
        /home/zheng/STUDY/GoWork/demo/main.go:11 +0x39
panic(0x466460, 0x48a298)
        /usr/local/go/src/runtime/panic.go:965 +0x1b9
main.main()
        /home/zheng/STUDY/GoWork/demo/main.go:13 +0x68

图解:

panic4

recovery

这个函数的目的很简单,就是把_panic结构体中的recovered字段设为true,不过它在执行修改前会判断当前有没有panic和当前panic有没有被恢复过。可以通过源码runtime.gorecover中体现出来

func gorecover(argp uintptr) interface{} {
	gp := getg()
	p := gp._panic
	if p != nil && !p.recovered && argp == uintptr(p.argp) {
		p.recovered = true
		return p.arg
	}
	return nil
}

所以recovery要通过deferpanic之后调用才会生效