GO成神之路:深入defer|Go主题月

791 阅读2分钟

说明

在Go语言中提供了一个非常有用并且好用的语法糖就是defer关键字,这个关键字的作用是:为了在方法执行完成后执行一些操作,通常这些操作应该是释放一些IO资源,比如:文件IO,网络IO等,主要是为了防止一些IO资源没有被释放从而导致内存泄漏。

另外,我们也可以借助defer做一些另外的操作,比如记录一些日志,处理panic等。

defer的本质

defer语法糖被编译后,会将defer后面的指定的func编译为以下结构体(摘自go源码)

runtime2.go

type _defer struct {
        // 略去部分代码
        ...
        // 主要代码如下:
        // fn字段指向defer关键字后定义的func地址
        fn        *funcval 
        // link指向下一个defer
	link      *_defer
}

从源代码中我们暂时挑选两个比较容易看懂的代码来解释:
1.fn字段: 这个字段保存的地址就是defer关键字之后定义的func
2. link字段: 这个字段指向下一个defer的定义,通过这个关键字这也就说明了defer定义的func执行顺序是按照链表的顺序执行的。

defer的执行顺序

通过下面这段简单的代码,我们来探究一下以下多个defer是按照怎么样的顺序被执行的。

package main

import "log"

func main() {
        // 下面两个defer的参数为不同类型,是为了方便后面的调式更清晰
	defer log.Println(1)
	defer log.Println("defer")
}

探究过程:

  1. 首先我们在两个defer上分别设置断点,并启动调试

    下面图片中我们可以看到,此时_defer参数为nil

image.png 2. 然后执行第一条defer

我们观察红框中的三个值

image.png 3. 最后执行第二条defer

通过红框中的值,细心的你应该发现了,此时_defer链表中第二个defer定义的func处于链表的第一个元素,如果main函数执行完成后,defer被调用时,遍历_defer链表,那么最先执行的肯定是第二个defer定义的函数。

image.png

通过以上简单的例子,我们得出了以下结论:defer的执行是按照先进后出的顺序被调用的,也就是栈模型