【Go笔记】defer延迟调用

特性

  • defer延迟调用是Go的一个很有特色的特性了,按照LIFO的次序调用,能确保闭包或函数调用在函数结束的时候被执行
  • 一般用于资源释放以及异常panic的处理
  • defer有时候可能不会被执行,比如放在if中但是条件不成立的语句
  • 可以用于中间件,并用于整个goroutine的资源释放,错误处理操作等等。
  • 参数预计算 - 即当程序执行到defer的时候,defer函数会捕获到当前的参数的值并保存好,等到函数结束的时候执行;但是如果直接使用外部参数,就没有这个问题,会自动从参数的地址读取出最后修改的值来进行函数运行。简而言之,传实参会预处理,传形参不会

优势

  • 资源释放 - 在每个资源后都立即加入defer语句,都将保证被执行
  • 异常捕获 - 与recovery搭配使用,进行正常的函数执行流程。panicrecover需要在同一个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/
Author
Stephen Zeng
Posted on
August 20, 2025
Licensed under