【defer】1.12版本中的defer|Go主题月

320 阅读2分钟

defer的作用

defergo中的作用就是延迟执行,一般常用来关闭一些io流等。简单来个例子:

package main

import "fmt"

func main() {
	fmt.Println("1")
	defer func() {
		fmt.Println("2")
	}()
	fmt.Println("3")
}

上面的例子输出:

1
3
2

从上面得知defer会在程序结束前被执行,但是如果有多个defer呢?这时候go是执行的呢?

多个defer执行流程

go1.12版本中,defer对应的是一个结构体_defer。每个gogoroutine上都有一个指向这个结构体的指针*_defer。所有_defer通过结构体中的link组成一个链表。每添加一个_defer都会在链表头部上插入,所以就导致了先入后出现象。**所有的_defer**结构体都在堆内分配内存。

频繁的堆分配势必影响性能,所以Go语言会预分配不同规格的deferpool,执行时从空闲_defer中取一个出来用。没有空闲的或者没有大小合适的,再进行堆分配。用完以后,再放回空闲_defer池。这样可以避免频繁的堆分配与回收。

defer1

通过上面的流程,我们就可以理解多个defer的输出流程了

package main

import "fmt"

func main() {

	fmt.Println("1")
	defer func() {
		fmt.Println("2")
	}()
	defer func() {
		fmt.Println("3")
	}()
	defer func() {
		fmt.Println("4")
	}()
	fmt.Println("5")
}

输出:

1
5
4
3
2

defer的优化

上面说到每个defer都是由一个个结构组成的,所以go会对多个同样的没有捕获参数的defer做出优化。具体看图解

defer4

defer的参数取值

defer注册的时候,编译器会在它自己的个参数后面,开辟一段空间,用于存放defer函数的返回值和参数。这一段空间会在注册defer时,直接拷贝到_defer结构体的后面。defer5

所以就可以解释下面这段代码了:

package main

import "fmt"

func main() {
	i:=1
	defer T1(i)
	i++
}
func T1(i int)  {
	fmt.Println(i)
}

输出:

1