一、defer 原则
首先defer 后面通常只会跟一个函数调用, 这个函数调用主要是下面这种:
defer func() {
fmt.Println("start")
}()
假如在这个函数中出现了一个变量, 那么我们需要分析这个变量的位置
- 如果是在函数参数的位置, 那么这个变量会被复制:
func test5() {
i := 1
defer func(i int) {
i += 1
}(i)
}
- 如果变量位于函数体中的, 那么会分析这个变量从哪一个地方来
func test4() (r int) {
defer func() {
r = r + 5
}()
r = 10
return r
}
所以它的实际过程应该是下面这种:
func test4() int {
r = 10
func() {
r = r + 5
}()
return 15
}
二、defer 源码
在Go 语言中多个 defer 形成一个链表. defer 语句会首先调用一个 defer proc 函数, new 一个对应的结构体挂载到对应的G 上面调用new 之前会从 G 所绑定的 P 的 defer pool 里面取, 没有取到会从全局的defer pool里取, 实在没有的话才新建一个。这是 Go runtime 里非常常见的操作,即设置多级缓存,提升运行效率
在执行 RET 指令之前(注意不是 return 之前),调用 defer return 函数完成 _defer 链表的遍历,执行完这条链上所有被 defered 的函数(如关闭文件、释放连接、释放锁资源等)。在 deferreturn 函数的最后,会使用 jmpdefer 跳转到之前被 defered 的函数,这时控制权从 runtime 转移到了用户自定义的函数。这只是执行了一个被 defered 的函数,那这条链上其他的被 defered 的函数, 如何得到执行?

func example() {
defer func() {
fmt.Println("第二个 defer")
}()
defer func() {
fmt.Println("第一个 defer")
}()
// 其他代码...
}
- 当函数 example 即将返回时,Go运行时会开始执行_defer链表中的函数。
- 首先执行的是“第二个 defer”,因为它是最后被添加到链表中的。
- 在“第二个 defer”执行完毕后,jmpdefer 会被调用来将控制权返回到Go运行时的 deferreturn 函数。
- deferreturn 函数接着会执行_defer链表中的下一个函数,也就是“第一个 defer”。
TEXT runtime·gogo(SB), NOSPLIT, $16-8
最后一个参数是$16-8 函数的参数信息, 这个信息是给函数调用者看的, 函数调用者可以根据这些信息去构造自己的栈帧大小
具体的执行过程如下:
- 确定调用函数的参数大小
- 执行 call 指令 ----> 确定调用函数的执行位置
- 确定 call 指令返回的地址
- 确定当前函数的地址 ----> 之后方便恢复对应的栈
- 执行子函数
defer 函数调用执行过程:
func deferreturn() {
var p _panic
p.deferreturn = true
//getcallerpc()的值是调用defer_return函数的程序计数器
//getcallersp()函数返回调用deferreturn函数的函数的栈指针(SP)值。
p.start(getcallerpc(), unsafe.Pointer(getcallersp()))
for {
//下一份defer
fn, ok := p.nextDefer()
if !ok {
break
}
fn()
}
}
1261

被折叠的 条评论
为什么被折叠?



