defer是一种延迟函数,每增加一个defer函数会将其对应的_defer结构体压栈,在执行的时候是后压栈的defer先执行
1、defer触发的场景主要有三
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking
1)主函数return返回时,在return执行之前
2)主函数执行结束时
3)当前goroutine发生panic
2、官方对defer也提出3条规则
1)延迟函数的参数在defer语句出现时就已经确定下来了
这是因为在defer语句出现的时候,同时把需要的参数拷贝到栈上,后续主函数对该参数进行改变,不会影响defer中的值
但是指针的情况下,我们可以看下有什么不同:
2)延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
3)延迟函数可以操作主函数的具名返回值
可以先看一下_defer结构体字段,无特殊版本标记都是1.2版本出现的字段
通过看汇编代码,可以看到在1.14版本,会有如下三种情况处理defer
在1.12版本使用的是堆分配,1.13版本加入栈分配,现在1.14版本又加入开放源码,可以看到堆分配是最后的兜底方案。现在我们会来介绍每一种方案。
声明defer关键字处使用deferproc() 注册defer处理函数,将对应的_defer结构体值拷贝到堆上。
对于新创建好的defer结构体会添加在defer链表的表头,goroutine的defer指针指向链表的表头,这也是为什么倒序执行defer的原因。

没有捕获列表(没有外部函数使用的变量)的funcval(闭包) ,在编译阶段会进行优化 在只读数据段分配一个共用的funcval结构体。但当有捕获列表的funcval 在堆里分配funcval结构体的大小,捕获列表使用的变量在堆中会存储该变量,其他地方使用该变量则使用该变量的地址,所以值改变的时候引用的地方同时改变
主要流程是获取一个siz大小的_defer结构体,可以在协程对应的处理器的defer池获取,若获取不到,需要在堆上分配size大小的结构体,然后将该结构体放在链表头。
在ret指令返回时,插入deferreturn(),将defer链表的头取出执行。 执行时,会将值从堆上拷贝到栈上。
分析go1.12 defer的缺点
_defer结构体是堆分配,即使有预分配的defer池,也需要在堆上获取和释放,参数还要在堆栈间来回拷贝
使用链表注册defer信息,链表本身操作较慢
声明defer关键字处使用deferproc() 注册defer处理函数,把栈上的defer结构体注册到defer链表中。
defer执行时,依然是通过deferreturn实现的,在defer函数执行时拷贝参数,不过不是在堆栈间,而是在栈上的局部变量空间拷贝到栈上的参数空间,性能提升30%
所以1.3的优化点:主要减少defer信息的堆分配。但是在显示和隐式的defer处理(例如for循环)还是使用defer1.2
1)开放编码的使用是有条件的,满足以下3个条件才会启用开放源码
延迟比特和延迟记录是开放源码的主要结构:
延迟比特中的每一个比特位都表示该位对应的 defer 关键字是否需要被执行。例如下面最后1位是1,说明接下来该位的defer会执行,使用或运算,把df的第一位置为1,标志该位可以执行,在执行的时候判断即可,判断完置为0,避免重复执行
在编译阶段插入代码,把defer的执行逻辑展开在所属函数内,从而免于创建defer结构体,去了构造defer链表项,并注册到链表的过程,不需要创建defer链表。
openDeferRecord主要记录了defer的信息保存在openDeferInfo结构体中。
2)在延迟执行的时候,调用deferreturn函数进行执行。把defer需要的参数定义为局部变量,在函数返回前,直接调用defer的函数
在执行的时候先获取deferBits,然后判断哪个defer该进行执行
1.14的缺点是:在某个defer函数执行过程中发生panic,后面的defer就不会被执行,就要去执行deferl链表了,但是这时open coded defer并没有注册到链表,这个时候需要额外通过栈扫描的方式来实现,所以panic变得慢了,但是panic发生的几率更小
这就是defer三种实现的原理~