【Go笔记】defer延迟调用
特性
defer
延迟调用是Go的一个很有特色的特性了,按照LIFO的次序调用,能确保闭包或函数调用在函数结束的时候被执行- 一般用于资源释放以及异常panic的处理
defer
有时候可能不会被执行,比如放在if
中但是条件不成立的语句- 可以用于中间件,并用于整个goroutine的资源释放,错误处理操作等等。
- 参数预计算 - 即当程序执行到defer的时候,defer函数会捕获到当前的参数的值并保存好,等到函数结束的时候执行;但是如果直接使用外部参数,就没有这个问题,会自动从参数的地址读取出最后修改的值来进行函数运行。简而言之,传实参会预处理,传形参不会
优势
- 资源释放 - 在每个资源后都立即加入
defer
语句,都将保证被执行 - 异常捕获 - 与
recovery
搭配使用,进行正常的函数执行流程。panic
和recover
需要在同一个goroutine中
返回值陷阱
- 先返回,再执行
defer
?并不是 return
并不是一个原子操作,会经历保存返回值到栈上 --> 执行defer --> 函数返回
,而第一步操作如果是在return
语句中执行的,那么执行defer
就不会影响到返回值;如果是提前就保存好的,那么defer
就会影响到返回值- 最好不要在
defer
中修改返回值,尤其是命名返回值 - 只要返回值是命名返回值,并且 defer 直接修改了命名返回变量本身,defer 就能更改返回值
底层原理
¶发展
- 早期版本的
defer
是放在堆中的,因此性能开销比一般函数调用要大 - 从1.13开始,对于单次调用的defer,就放在栈的一条链表当中完成LIFO;但是对于循环体内调用的defer则还是放在堆中等待执行。
- 1.14开始对常用的defer语句做编译实时内联优化,进一步提升了性能,因此在1.14之后,有内联,栈分配,堆分配这三种实现方式
¶堆分配
- 大部分情况下只会在循环结构中出现,循环内调用
defer
的时候会调用runtime.deferproc()
函数 deferproc
函数做的事情大致为计算调用者在栈中的位置;在堆中分配新的_defer
结构体并插入协程的_defer
链表头部;将调用者栈中位置记录到_defer
结构体中,并复制到堆中- 分配新的defer是时候,如果在全局和局部缓冲池都找不到对象时,需要在堆区分配指定大小的defer,当执行完被销毁后,会重新回到局部缓冲池中,再回到全局缓冲池中,再被垃圾回收
- 遍历执行defer链表时,遇到
nil
或者存储的SP地址不符合时都要终止执行 jmpdefer
通过更改返回地址,将程序引导到deferreturn中,复用栈空间,不会因为大量调用导致栈溢出
【Go笔记】defer延迟调用
https://study.0x535a.cn/go-note/go-defer/