Golang的defer底层原理是怎么样的?

前言

如果你用过 defer ,那么你会大概知道 defer 是用于延迟函数的调用。每次调用 defer 都会将一个函数压入栈中,执行的时候从栈顶取出函数来执行,那么它底层的原理到底是怎么样的?今天我们一起来探究一下。

defer 的使用规则

延迟函数的参数在defer语句出现时就已经确定下来了
func test() {
    num := 0
    defer fmt.Println(num)
    num = 3
    return
}
输出:0

在定义 defer fmt.Println(num) 的时候,参数就已经复制了一份,传递过去了,所以输出的结果为 0

延迟函数执行按后进先出顺序执行,即先出现的 defer最后执行

即是后进先出,类似于进栈出栈,如果你接着往下看,就会了解到,它底层就是维护了一个单链表。新建 defer 的时候,就压入单链表的头部,调用的时候,就从头部取出一个 defer,这样就有了先进后出的效果
在这里插入图片描述

延迟函数可能操作主函数的具名返回值(面试常考)

怎么理解这句话呢?

定义一个函数,如果这个函数的返回值是匿名的,那么 defer 不会影响到这个匿名返回值

定义一个函数,如果这个函数的返回值是具名的,就是我给这个返回值定义了变量名,那么 defer 可能会影响到这个 具名返回值。

有朋友说,你光说这么两句话,我也不太理解这是什么意思呀?别急,本文的最后一个小节,我会举例说明一下。

defer 底层数据结构

go/src/runtime/runtime2.go

defer 的结构体如下,

type _defer struct {
	siz     int32 // includes both arguments and results
	started bool
	heap    bool
	openDefer bool
	sp        uintptr  // stack point 栈指针
	pc        uintptr  // process count 程序计数器
	fn        *funcval // can be nil for open-coded defers
	_panic    *_panic  // panic that is running defer
	link      *_defer  // 指向下一个 defer,因为这是一个单链表
	fd   unsafe.Pointer // funcdata for the function associated with the frame
	varp uintptr       
	framepc uintptr
}

goroutine 的结构体如下,比较主要的字段,我都加了注释。可以看出,这里是有一个专门的字段用来保存 defer 的,它的底层是一个单链表。每次定义一个 defer 都会将 defer 插入到单链表头部,每次执行时,都从单链表头部获取 一个 defer,这样就有后进先出的执行效果。

type g struct {
	stack       stack   // 协程的 栈,
	sched     gobuf  // 在协程切换的时候,用于保存上下文
	goid         int64  //goroutine 的ID
	gopc           uintptr         // pc of go statement that created this goroutine
	startpc        uintptr         // pc of goroutine function
	_defer    *_defer // defer 指针,指向一个 defer 单链表,每次定义一个 defer 都会插入到单链表头部,每次执行时,都从单链表头部获取 一个 defer,这样就有后进先出的执行效果。
	此处省略很多参数

defer 指针,指向一个 defer 单链表,每次定义一个 defer 都会插入到单链表头部,每次执行都从从不获取,这样就有后进先出的执行效果。

那么,基于上面的解释,我们来画图理解一下:
在这里插入图片描述

defer 的创建和执行

源码在

/usr/local/go/src/runtime/panic.go

我们可以找到 deferproc() 和 deferreturn() 这两个函数,这两个方法分别用于创建和执行 defer。

deferproc() 做了什么事情呢?
  • 获取 deferproc之前的 sp 寄存器的值
  • 创建一个 _defer 结构体,然后把这个 _defer 放到 goroutine 的 *_defer 字段,并且将他放到当前的 _defer 链表的头
  • 初始化 _defer 结构体的参数
  • return0
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
	gp := getg() //获取一个 goroutine
	if gp.m.curg != gp {
		// go code on the system stack can't defer
		throw("defer on system stack")
	}

	if goexperiment.RegabiDefer && siz != 0 {
		// TODO: Make deferproc just take a func().
		throw("defer with non-empty frame")
	}
	sp := getcallersp() //获取当前寄存器的值
	argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
	callerpc := getcallerpc()

	d := newdefer(siz)  //创建 defer 结构体实例
	if d._panic != nil {
		throw("deferproc: d.panic != nil after newdefer")
	}
	d.link = gp._defer
	gp._defer = d //给 goroutine 的单链表赋值
	d.fn = fn
	d.pc = callerpc
	d.sp = sp
	switch siz {
	case 0:
		// Do nothing.
	case sys.PtrSize:
		*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
	default:
		memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
	}
	return0()
}

deferreturn():在return指令,准确的讲是在ret指令前调用,其将defer从goroutine链表中取出并执行。
可以简单这么理解,在编译在阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()。

  • 获取 g、获取 p
  • 执行 defer
  • 运行结束后释放 defer
func deferreturn() {
	gp := getg()
	d := gp._defer
	if d == nil {
		return
	}
	sp := getcallersp()
	if d.sp != sp {
		return
	}
	if d.openDefer {
		done := runOpenDeferFrame(gp, d)
		if !done {
			throw("unfinished open-coded defers in deferreturn")
		}
		gp._defer = d.link
		freedefer(d)
		return
	}
	argp := getcallersp() + sys.MinFrameSize
	switch d.siz {
	case 0:
		// Do nothing.
	case sys.PtrSize:
		*(*uintptr)(unsafe.Pointer(argp)) = *(*uintptr)(deferArgs(d))
	default:
		memmove(unsafe.Pointer(argp), deferArgs(d), uintptr(d.siz))
	}
	fn := d.fn
	d.fn = nil
	gp._defer = d.link
	freedefer(d)
	_ = fn.fn
	jmpdefer(fn, argp)
}

defer 对于不同情况的处理

匿名返回值
func test() (int) {
	t := 2
    defer func() {
        t = 10
    }()

    return t
}

func main() {
    fmt.Println(test())
}
输出 2
具名返回值
func test() (t int) {  //t初始化0, 并且作用域为该函数全域

    defer func() {
        t = t * 10
    }()

    return 1
}

func main() {
    fmt.Println(test())
}
输出 10
发生 panic
func main() {
    defer_call()

    fmt.Println("main 正常结束")
}

func defer_call() {
    defer func() { fmt.Println("defer: panic 之前1") }()
    defer func() { fmt.Println("defer: panic 之前2") }()

    panic("异常内容")  //触发defer出栈

	defer func() { fmt.Println("defer: panic 之后,永远执行不到") }()
}
输出:
defer: panic 之前2
defer: panic 之前1
panic: 异常内容
//... 异常堆栈信息
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值