GO 中 defer的实现原理

我们来回顾一下上次的分享,分享了关于 通道的一些知识点

  • 分享了 GO 中通道是什么
  • 通道的底层数据结构详细解析
  • 通道在GO源码中是如何实现的
  • Chan 读写的基本原理
  • 关闭通道会出现哪些异常,panic
  • select 的简单应用
chan

defer 是什么

defer

是 GO 中的一个关键字

return
defer后进先出 LIFO

遵循后进先出原则

后进入栈的,先出栈

先进入栈的,后出栈

队列

遵循先进先出 , 我们就可以想象一个单向的管道,从左边进,右边出

先进来,先出去

后进来,后出去,不准插队

defer 实现原理

咱们先抛出一个结论,先心里有点底:

deferdeferprocdeferreturndeferreturn
deferdefer
src/runtime/runtime2.gotype _defer struct {
_defer
tag说明
sizdefer函数的参数和结果的内存大小
fn需要被延迟执行的函数
_panicdefer 的 panic 结构体
link同一个协程里面的defer 延迟函数,会通过该指针连接在一起
heap是否分配在堆上面
openDefer是否经过开放编码优化
sp栈指针(一般会对应到汇编)
pc程序计数器

defer 关键字后面必须是跟函数,这一点咱们要记住哦

defer
  • 栈指针 SP
  • 程序计数器 PC
  • 函数的地址
link
linkdeferdeferdefer
deferdefer
deferdefer

咱们来画个图形象一点

deferdefer test1()
defer test2()
deferdefer

咱们取的时候也是一直取头下来执行,直到单链表为空。

咱一起来看看defer 的具体实现

src/runtime/panic.godeferproc

deferproc 的作用是

fnthis
getcallersp()
deferprocrsp
callerpc := getcallerpc()
rspcallerpcdeferproc
d := newdefer(siz)
d := newdefer(siz)deferdefer

咱看看 deferproc 的大体流程

deferprocnewdefer_defer

来我们看看 newdefer的源码

src/runtime/panic.gonewdefer
newderfer
Defer
deferdeferdefer
getg()

获取当前协程的结构体指针

pp := gp.m.p.ptr()

拿到当前工作线程里面的 P

然后拿到 从全局的对象池子中拿一部分对象给到P的池子里面

_defer
sched.deferpool[sc]pp.deferpool[sc]

mallocgc分配空间

mallocgcsizheap
mallocgcsrc/runtime/malloc.go

最后再来看看return0

deferprocreturn0()
return0deferproc0
deferprocGodeferreturn
return0
return0deferreturn

简单说下 deferreturn

deferreturndeferGC

GO 中 defer 的规则

deferdefer
deferdeferdeferdefer

咱们还是要来解释一下的,上面第 2 点,应该都好理解,上面的图也表明了 执行顺序

第一点咱们来写个小DEMO

defer

别猜了,运行结果是 1,小伙伴们可以将代码拷贝下来,自己运行一波

第三点也来一个DEMO

延迟函数可能会影响到整个函数的返回值

test3
return

res = 1

res++

因此,结果就是 2

总结

  • 分享了defer是什么
  • 简单示意了栈和队列
  • defer的数据结构和实现原理,具体的源码展示
  • GO中defer的 3 条规则