golang之defer简介
golang之defer简介
[TOC]
golang之defer简介
每次defer语句执行的时候,会把函数“压栈”,函数参数会被拷贝下来;当外层函数(非代码块,如一个for循环)退出时,defer函数按照定义的逆序执行;如果defer执行的函数为nil, 那么会在最终调用函数的产生panic.
defer语句并不会马上执行,而是会进入一个栈,函数return前,会按先后出的顺序执行。也说是说最先被定义的defer语句最后执行。先进后出的原因是后面定义的函数可能会依赖前面的资源,自然要先执行;否则,如果前面先执行,那后面函数的依赖就没有了。
在defer函数定义时,对外部变量的引用是有两种方式的,分别是作为函数参数和作为闭包引用。作为函数参数,则在defer定义时就把值传递给defer,并被cache起来;作为闭包引用的话,则会在defer函数真正调用时根据整个上下文确定当前的值。
defer后面的语句在执行的时候,函数调用的参数会被保存起来,也就是复制了一份。真正执行的时候,实际上用到的是这个复制的变量,因此如果此变量是一个“值”,那么就和定义的时候是一致的。如果此变量是一个“引用”,那么就可能和定义的时候不一致。
1func main() {
2 fmt.Println(increaseA()) // 0
3 fmt.Println(increaseB()) // 1
4 fmt.Println(f1()) // 10
5 fmt.Println(f2()) // 5
6 fmt.Println(f3()) // 0
7 fmt.Println(f4()) // 2
8 fmt.Println(f5()) // 2
9}
10
11func increaseA() int {
12 var i int
13 defer func() { i++ }()
14 return i
15}
16
17func increaseB() (r int) {
18 defer func() { r++ }()
19 return r
20}
21
22
23func f1() (r int) {
24 r = 5
25 defer func() { r = r + 5 }()
26 return r
27}
28
29func f2() (r int) {
30 t := 5
31 defer func() { t = t + 5 }()
32 return t
33}
34
35func f3() (r int) {
36 defer func(r int) { r = r + 5 }(r)
37 return r
38}
39
40func f4() (r int) {
41 i := 1
42 defer func() {
43 r++
44 }()
45 return i
46}
47
48func f5() (r int) {
49 defer func() {
50 r++
51 }()
52 return 1
53}
54
return xxx 这条语句经过编译之后,变成了三条指令:
- 返回值 = xxx
- 调用defer函数
- 空的return
f2 拆解后:
1func f2() (r int) {
2 t := 5
3
4 // 1. 赋值指令
5 r = t
6
7 // 2. defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
8 func() {
9 t = t + 5
10 }
11
12 // 3. 空的return指令
13 return
14}
f4 拆解后:
1func f4() (r int) {
2 i := 1
3 // 1. 赋值指令
4 r = i
5 defer func() {
6 r++
7 }()
8 // 3. 空的return指令
9 return
10}
11
f5 拆解后:
1func f5() (r int) {
2 // 1. 赋值指令
3 r = 1
4 defer func() {
5 r++
6 }()
7 // 3. 空的return指令
8 return
9}
10
1package main
2
3import "fmt"
4
5type number int
6
7func (n number) print() { fmt.Println(n) }
8func (n *number) pprint() { fmt.Println(*n) }
9
10func main() {
11 var n number
12
13 defer n.print() //0
14 defer n.pprint() //3
15 defer func() { n.print() }() //3
16 defer func() { n.pprint() }() //3
17 n = 3
18 // output:
19 // 3
20 // 3
21 // 3
22 // 0
23}
第四个defer语句是闭包,引用外部函数的n, 最终结果是3; 第三个defer语句同第四个; 第二个defer语句,n是引用,最终求值是3. 第一个defer语句,对n直接求值,开始的时候n=0, 所以最后是0;
三条基本规则
1. defer函数是在外部函数return后,按照后申明先执行(栈)的顺序执行的
延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
1package main
2
3import "fmt"
4
5func main() {
6 defer fmt.Println("1")
7 defer fmt.Println("2")
8 defer fmt.Println("3")
9}
10// output:
11// 3
12// 2
13// 1
2. defer函数的参数的值,是在申明defer时确定下来de
延迟函数的参数在defer语句出现时就已经确定下来了 注意:对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,defer后面的语句对变量的修改可能会影响延迟函数。
1package main
2
3import "fmt"
4
5func main() {
6 i := 0
7
8 defer fmt.Println(i) //(0) 这也算是作为defer函数的参数
9 defer func(j int) { fmt.Println(j) }(i) //(0) 作为参数
10 defer func() { fmt.Println(i) }() //(1) 作为闭包(closure)进行引用
11
12 i++
13}
14// output:
15// 1
16// 0
17// 0
1package main
2
3import "fmt"
4
5type Person struct {
6 name string
7}
8
9func main() {
10 person := &Person{"Lilei"}
11
12 defer fmt.Println(person.name) // person.name作为普通类型当做defer函数的参数
13 defer fmt.Printf("%v\n", person) // 引用类型作为参数
14 defer func(p *Person) { fmt.Println(p.name) }(person) // 同上
15 defer func() { fmt.Println(person.name) }() // 闭包引用,对引用对象属性的修改不影响引用
16
17 person.name = "HanMeimei"
18}
19// output:
20// HanMeimei
21// HanMeimei
22// &{HanMeimei}
23// Lilei
1package main
2
3import "fmt"
4
5type Person struct {
6 name string
7}
8
9func main() {
10 person := &Person{"Lilei"}
11
12 defer fmt.Println(person.name) // 同上,person.name作为普通类型当做defer函数的参数
13 defer fmt.Printf("%v\n", person) // 作为defer函数的参数,申明时指向“Lilei”
14 defer func(p *Person) { fmt.Println(p.name) }(person) // 同上
15 defer func() { fmt.Println(person.name) }() // 作为闭包引用,随着person的改变而指向“HanMeimei”
16
17 person = &Person{"HanMeimei"}
18}
19// output:
20// HanMeimei
21// Lilei
22// &{Lilei}
23// Lilei
3. defer函数可以读取和修改外部函数申明的返回值。
延迟函数可能操作主函数的具名返回值
1package main
2
3import "fmt"
4
5func main() {
6 fmt.Printf("output: %d\n", f())
7}
8
9func f() (i int) {
10 defer fmt.Println(i) // 参数引用
11 defer func(j int) { fmt.Println(j) }(i) // 同上
12 defer func() { fmt.Println(i) }() // 闭包引用
13 defer func() { i++ }() // 执行前,i=2
14 defer func() { i++ }() // 执行前,i=1
15
16 i++
17
18 return
19}
20
output
13
20
30
4output: 3
xxx
1package main
2
3import "fmt"
4
5func main() {
6 var whatever [3]struct{}
7
8 for i := range whatever {
9 defer func() {
10 fmt.Println(i)
11 }()
12 }
13}
14// 2
15// 2
16// 2
defer实现原理
1 defer数据结构
源码包src/src/runtime/runtime2.go:_defer定义了defer的数据结构:
1type _defer struct {
2 sp uintptr //函数栈指针
3 pc uintptr //程序计数器
4 fn *funcval //函数地址
5 link *_defer //指向自身结构的指针,用于链接多个defer
6}
我们知道defer后面一定要接一个函数的,所以defer的数据结构跟一般函数类似,也有栈地址、程序计数器、函数地址等等。
与函数不同的一点是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的单链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer时就从单链表表头取出一个defer执行。
下图展示一个goroutine定义多个defer时的场景:
从上图可以看到,新声明的defer总是添加到链表头部。
函数返回前执行defer则是从链表首部依次取出执行,不再赘述。
一个goroutine可能连续调用多个函数,defer添加过程跟上述流程一致,进入函数时添加defer,离开函数时取出defer,所以即便调用多个函数,也总是能保证defer是按FIFO方式执行的。
2 defer的创建和执行
源码包src/runtime/panic.go定义了两个方法分别用于创建defer和执行defer。
- deferproc(): 在声明defer处调用,其将defer函数存入goroutine的链表中;
- deferreturn():在return指令,准确的讲是在ret指令前调用,其将defer从goroutine链表中取出并执行。
可以简单这么理解,在编译在阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()。
return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转