NOTE

1.13 defer

1. 什么是defer 让函数可以在当前函数执行完毕后执行的一种机制。 这里的函数执行完毕包括通过return正常结束或者panic导致的异常结束 2. 为什么需要defer 减少资源泄露的发生 3. 如何使用 在创建资源语句的附件,使用defer释放资源 3.1. 关闭资源 3.2. defer和

Go创建于 更新于 约 1 分钟读完historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 什么是defer

让函数可以在当前函数执行完毕后执行的一种机制。 这里的函数执行完毕包括通过return正常结束或者panic导致的异常结束

2. 为什么需要defer

减少资源泄露的发生

3. 如何使用

在创建资源语句的附件,使用defer释放资源

3.1. 关闭资源

func TestDefer(t *testing.T) {
	f,err := os.Open("test.txt")
	//先处理error
	if err != nil {
		panic(err)
	}
	//记得判断f是否为空
	if f != nil {
		defer f.Close()
	}

}

3.2. defer和return

3.2.1. 执行顺序

defer是在return之前执行,顺序是:

  • 返回值 = xxx
  • 调用defer函数
  • 空的return

3.2.2. 例子1

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

func TestDefer0(t *testing.T) {
	fmt.Println(f())
}
//输出
1
  • return 0改写为result = 0 和 return
func f() (result int) {
	result = 0  //return语句不是一条原子调用,return xxx其实是赋值+ret指令
	func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
		result++
	}()
	return
}
//输出
1

3.2.3. 例子2

func TestDefer1(t *testing.T) {
	fmt.Println(f1())
}

// 跟上例的区别在于返回值没有明确命名
func f1() int {
	t := 5
	defer func() {
		t = t + 5
	}()

	return t
}

//输出
5
  • return t改写为r = t 和 return

func f1() (r int) {
     t := 5
     r = t //赋值指令
     func() {        //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
         t = t + 5
     }
     return        //空的return指令
}

3.2.4. 例子3


func f2() (r int) {
	defer func(r int) {
		r = r + 5
	}(r)
	return 1
}

func TestDefer2(t *testing.T) {
	fmt.Println(f4())
}


//输出
1
  • return 1改写成 r = 1 和 return
func f2() (r int) {
     r = 1  //给返回值赋值
     func(r int) {        //这里改的r是传值传进去的r,不会改变要返回的那个r值
          r = r + 5
     }(r)
     return        //空的return
}

3.3. 多个defer按顺序压入栈中

3.3.1. 普通传参

func TestDefer3(t *testing.T) {
	for i := 0; i < 10; i++ {
		defer fmt.Println(i) 
	}

	fmt.Println("退出了,执行defer")
}

//输出
退出了,执行defer
9
8
7
6
5
4
3
2
1
0

那么为什么是一个栈呢?后面定义的函数可能会依赖前面的资源,自然要先执行;否则,如果前面先执行,那后面函数的依赖就没有了

3.3.2. defer+闭包

上面的代码如果改成闭包形式那么输出结果会不一样

func TestDefer4(t *testing.T) {
	for i := 0; i < 10; i++ {
		defer func() {
			fmt.Println(i)
		}()
	}

	fmt.Println("退出了,执行defer")
}

//输出
退出了,执行defer
10
10
10
10
10
10
10
10
10
10

3.4. defer配合recover

如果程序有异常会panic,panic会停止当前应用程序而不仅是当前协程,如果要在panic后的程序进行一些资源的收尾处理,需要用defer+recover

func TestDefer5(t *testing.T) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("program panic. err=", err)
		}
	}()
	panic("程序崩溃")
}
//输出
program panic. err= 程序崩溃

4. 原理

func TestDefer6(t *testing.T) {
	test()
}

func test() {
	var i = 0
	defer func() {
		i++
	}()
	return
}
  • go tool compile -S
0x0054 00084 (defer1_test.go:13)	CALL	runtime.deferprocStack(SB)
0x0059 00089 (defer1_test.go:13)	TESTL	AX, AX
0x005b 00091 (defer1_test.go:13)	JNE	109
0x005d 00093 (defer1_test.go:16)	XCHGL	AX, AX
0x005e 00094 (defer1_test.go:16)	CALL	runtime.deferreturn(SB)
0x0063 00099 (defer1_test.go:16)	MOVQ	72(SP), BP
0x0068 00104 (defer1_test.go:16)	ADDQ	$80, SP
0x006c 00108 (defer1_test.go:16)	RET
0x006d 00109 (defer1_test.go:13)	XCHGL	AX, AX
0x006e 00110 (defer1_test.go:13)	CALL	runtime.deferreturn(SB)
0x0073 00115 (defer1_test.go:13)	MOVQ	72(SP), BP
0x0078 00120 (defer1_test.go:13)	ADDQ	$80, SP
0x007c 00124 (defer1_test.go:13)	RET
  • 在defer出现的地方,插入了指令CALL runtime.deferprocStack(SB);
  • 在函数返回之前的地方,插入指令CALL runtime.deferreturn(SB)
//普通的函数返回
add xx SP
return

//带defer语句的返回
call runtime.deferreturn,
add xx SP
return

5. 参考

讨论

使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看