【go上一层楼】浅谈defer

文章目录一、Defer结构二、defer的基本原理分配函数执行defer函数链jmpdefer三、总结四、参考一、Defer结构defer本质其实是一个_defer结构体。type _defer struct { siz int32 // 参数和返回值的内存大小 started bool heap bool // 区分该结构是在栈上分配的,还是对上分配的 sp uintptr // sp 计数器值,栈指针; pc
摘要由CSDN通过智能技术生成


一、Defer结构

defer本质其实是一个_defer结构体。

type _defer struct {
   
    siz     int32 // 参数和返回值的内存大小
    started bool
    heap    bool    // 区分该结构是在栈上分配的,还是对上分配的
    sp        uintptr  // sp 计数器值,栈指针;
    pc        uintptr  // pc 计数器值,程序计数器;
    fn        *funcval // defer 传入的函数地址,也就是延后执行的函数;
    _panic    *_panic  // panic that is running defer
    link      *_defer   // 链表
}

从defer结构体中可以看到有个成员变量link,他的作用是指向另一个defer结构体,所以多个defer通过link连接,组成一个链表。
而哪里是该链表的表头呢?
在goroutine结构体中也有defer指针的成员变量,其正是defer链表的表头。

在这里插入图片描述

二、defer的基本原理

defer逻辑主要分为两步骤:

  • 空间分配
  • 延迟函数调用

且这两步骤也不是紧挨执行的。

编译器其实会在编译defer deferTestDefer() 这一的时候,会编译成两个函数:

  • 分配函数(空间分配,在defer语句出执行)
  • 执行函数(延迟调用函数,在return返回后执行)

在1.13之前defer对应的会被编译成两个函数:

  • 分配函数:deferproc
  • 执行函数:deferreturn

在1.13之后,分配函数增添了deferprocStack函数,因为有了该函数,整体defer的性能提升了30%

  • 分配函数:deferproc & deferprocStack
  • 执行函数:deferreturn

deferproc和deferprocStack这两个函数区别是什么,为什么增加了deferprocStack函数后,就使得性能提升了呢?
因为deferproc的空间分配是在堆上分配,而deferprocStack则的在栈上。
(后面我们会再详细说关于空间分配的一些细节)

接下来看这段代码

func deferTestDefer(p int)int{
   
 p+=1
 return  p
}
func defertest(){
   
	
	defer deferTestDefer(10)
	return 
}

在编译器编译到 defer deferTestDefer(10)的时候,会先执行分配函数,会将当前defer相关的能用到的空间全都准备好。比如:

  • 构造_defer结构体所需要的空间
  • 延迟函数deferTestDefer的参数所需要的空间
  • 延迟函数deferTestDefer的返回值所需要的空间
    在这里插入图片描述
    由图中可以看出:_defer结构处于这个内存快的头部位置,结构紧跟的是延迟函数的参数和返回值空间,而大小由_defer.siz指定。所以这块内存的值其实是在defer执行的时候(而不是return的时候)就已经填充好了,所以对于在return的时候执行的延迟调用函数,其参数都是预分配的。

注意上述我们所说的几个关键点:

  • 延迟调用函数相关的参数,是在执行defer的时候预分配的,而不是在return的时候现分配的
  • 真正执行defer对应的函数,是在return的时候,但是值早就在定义defer的时候已经备好了

问题:defer怎么传递参数
对于defer,延迟回调函数的参数和返回值其实在执行deferprocStack/dederproc的时候就已经分配好了。并不是等到真正执行延迟回调函数的时候才去获取
go语言中一个函数的参数由caller函数准备好,所以在执行延迟回调函数的时候,deferreturn会把defer延迟回调函数需要的参数准备好(空间和值)

func deferreturn(arg0 uintptr) {
   

	...
    switch d.siz {
   
    case 0:
        // Do nothing.
    case sys.PtrSize:
        *(*uintptr)(unsafe.Point
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值