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 | 说明 |
|---|---|
| siz | defer函数的参数和结果的内存大小 |
| fn | 需要被延迟执行的函数 |
| _panic | defer 的 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 条规则