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 这条语句经过编译之后,变成了三条指令:

  1. 返回值 = xxx
  2. 调用defer函数
  3. 空的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跳转