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 的函数, 如何得到执行?

defer 源码分析_第1张图片

func example() {
    defer func() {
        fmt.Println("第二个 defer")
    }()
    defer func() {
        fmt.Println("第一个 defer")
    }()
    // 其他代码...
}
  1. 当函数 example 即将返回时,Go运行时会开始执行_defer链表中的函数。
  2. 首先执行的是“第二个 defer”,因为它是最后被添加到链表中的。
  3. 在“第二个 defer”执行完毕后,jmpdefer 会被调用来将控制权返回到Go运行时的 deferreturn 函数。
  4. deferreturn 函数接着会执行_defer链表中的下一个函数,也就是“第一个 defer”。
TEXT runtime·gogo(SB), NOSPLIT, $16-8

最后一个参数是$16-8 函数的参数信息, 这个信息是给函数调用者看的, 函数调用者可以根据这些信息去构造自己的栈帧大小
具体的执行过程如下:

  1. 确定调用函数的参数大小
  2. 执行 call 指令 ----> 确定调用函数的执行位置
  3. 确定 call 指令返回的地址
  4. 确定当前函数的地址 ----> 之后方便恢复对应的栈
  5. 执行子函数

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()
	}
}

你可能感兴趣的:(软件随想,go)