在Go语言里面 有一个关键字 我们经常会碰到 就是 defer ,关于defer我们需要记住以下几点:
1.defer语句会在该函数结束的时候被调用,即使后面的语句运行时出现异常了defer语句仍然会被执行。
2.如果defer语句中引用了参数,则该参数的值将是程序到defer这一行的时候的值,而与后面的语句没有关系。
因此,defer通常用来释放函数内部变量。
在看下面的代码:
func test01() {
fmt.Println("我是测试01")
a := 10
defer fmt.Println("a=", a)
a++
}
//未引用参数 跟测试一一样
func test02() {
fmt.Println("我是测试02")
a := 10
defer func() {
fmt.Println("a=", a)
}()
a++
}
//此处引用了参数
func test03() {
fmt.Println("我是测试03")
a := 10
defer func(a int) {
fmt.Println("a=", a)
}(a)
a++
}
解释:
tese01 :代码执行到 defer 那一行时候 因为此时引用了参数,所以 此时的数值 就是当前a的数值 跟后面无关.所以 a=10
test02:正常执行完毕之后 a的数值 就是程序执行完事之后的数值 所以a=11.
test03:因为defer 引用了参数 ,跟test01一个道理
上面的是简单的一个调用,目的是告诉defer 的作用 以及简单说明,那么当我们在同时使用多个defer 时候会出现什么结果呢?会不会按照代码调用顺序来呢?我们继续用代码来看:
func test01() {
fmt.Println("我是测试01")
a := 10
defer fmt.Println("第一次 a=", a)
defer fmt.Println("第二次 a=", a)
a++
}
结果:
("第二次 a=", a) 这个被先输出了,这是为什么呢?
这是因为每一个协程都会维护一个延迟调用堆栈,按照代码顺序把需要延迟调用的函数压入栈中,当函数进入退出阶段后,就会从延迟调用堆栈中取出需要执行的函数调用并执行。按照先进后出的原则来执行.
defer的数据结构:
type _defer struct {
siz int32 //参数大小
started bool // defer是否被调用过的标识
sp uintptr // sp at time of defer pc uintptr
fn *funcval // defer 后面跟的function _panic *_panic // panic that is running defer
link *_defer // 链表结构
defer后面一定要接一个函数的,所以defer的数据结构根一般函数类似,也有栈指针、程序计数器、
函数地址等等。
与函数不同的一点是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer就从单链表表头取出一个defer执行。
在deferproc命令的源码中看到这一块代码:
deferproc命令会创建一个_defer类型的数据,它实际上是一个链表类型,有一个字段link指向下一个_defer数据。在当前协程上会保存这个_defer链表的头部,每次创建defer的时候就会插入到协程_defer链表的头部,形成一个基于链表的堆栈。
看看deferreturn命令的源码:
当我们进入退出阶段,执行deferreturn命令的时候,会从当前协程的_defer链表中取出头部,并把下一个元素作为_defer链表的头部,然后再使用jmpdefer指令完成跳转调用,jmpdefer完全是使用汇编完成.
3.defer中使用闭包函数的时候,只有最后一次调用是被延迟执行的
看下面的代码:
func main() {
fmt.Println("a1")
defer test04()()
fmt.Println("a2")
}
func test04() func() {
fmt.Println("我是测试04第一次调用")
b:= func() {
fmt.Println("我是测试04第二次调用")
}
return b
}
错误:
按照我们自己理解的打印结果是:
a1 a2 我是测试04第一次调用,我是测试04第二次调用
正确结果:
解释:当程序运行的时候,首先会打印出 a1,到test04函数 发现这个函数是一个defer修饰的,会进去查看 ,内部又是一个连续调用的,那么就会将最后一次调用延迟,其它的正常输出,所以 就出现上图那样的结果.
这就可以解释:
defer中使用闭包函数的时候,只有最后一次调用是被延迟执行的.