Defer

defer 用于注册延迟调用:函数返回前、或 panic 发生时,被 defer 的语句会按照「后进先出」的顺序执行。它常用于资源释放(关闭文件、解锁 mutex、恢复 panic)。

执行时机的关键规则:return xxx 并不是原子操作,而是分两步——先把返回值赋值,再执行 defer,最后真正返回。因此 defer 中修改命名返回值会影响最终返回结果,而修改普通局部变量则不会。

  1. stack

包内执行过程中,defer 存储在栈空间 2. return

defer 执行在 return 后,先执行 return 的内容,在按照栈内存储顺序从栈顶向下依次执行 defer 语句 3. panic

遇到 panic 之后,按照出栈的方式执行 defer 函数,直到被 recover() 函数捕获

  • recover 函数捕获异常
  • 栈内的 defer 仍然会执行,直到空栈

Examples-Panic-Without-Recover

package main

import (
    "fmt"
)

func main() {
    defer_call()

    fmt.Println("main 正常结束")
}

func defer_call() {
    defer func() { fmt.Println("defer: panic 之前1") }()
    defer func() { fmt.Println("defer: panic 之前2") }()
    panic("异常内容")  //触发defer出栈
	defer func() { fmt.Println("defer: panic 之后,永远执行不到") }()
}
/**
defer: panic 之前2
defer: panic 之前1
panic: 异常内容
//... 异常堆栈信息**/

Examples-Panic-With-Recover

package main

import "fmt"

func main() {
	defer_call()

	fmt.Println("main 正常结束")
}

func defer_call() {
	defer func() { fmt.Println("defer: before recover()") }()

	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
		fmt.Println("defer: panic 之前1, 捕获异常")
	}()

	defer func() { fmt.Println("defer: panic 之前2, 不捕获") }()

	panic("异常内容") //触发defer出栈

	defer func() { fmt.Println("defer: panic 之后, 永远执行不到") }()
}

/**
defer: panic 之前2, 不捕获
异常内容
defer: panic 之前1, 捕获异常
defer: before recover()
main 正常结束**/  //recover()defer func()之前的defer也会执行,在输出Panic内容后,会执行完所有的defer栈

Examples-Multi-Panic-With-Recover

panic仅有最后一个可以被revover捕获

package main

import (
    "fmt"
)

func main()  {

    defer func() {
       if err := recover(); err != nil{
           fmt.Println(err)
       }else {
           fmt.Println("fatal")
       }
    }()

    defer func() {
        panic("defer panic")
    }()

    panic("panic")
}
/**
defer panic**/

Examples-Defer-With-Nested-Func

defer 下的函数包含子函数的话:

  • 在入栈函数的时候,需要获取该函数所有的参数
  • 因此如果函数中嵌套了子函数的话,在入栈时会先执行这个子函数
package main

import "fmt"

func function(index int, value int) int {
    fmt.Println(index)
    return index
}

func main() {
    defer function(1, function(3, 0))
    defer function(2, function(4, 0))
}
/**
defer压栈function1,压栈函数地址、形参1、形参2(调用function3) --> 打印3
defer压栈function2,压栈函数地址、形参1、形参2(调用function4) --> 打印4
defer出栈function2, 调用function2 --> 打印2
defer出栈function1, 调用function1--> 打印1**/
package main

import "fmt"

func DeferFunc1(i int) (t int) {
	t = i //赋值t=1
	defer func() {
		t += 3 //defer 执行在return之后,返回 t = 4
	}()
	return t //t=1
}

func DeferFunc2(i int) int {
	t := i //t =1
	defer func() {
		t += 3 //局部变量
	}()
	return t //将t赋值给返回的值 返回t=1
}

func DeferFunc3(i int) (t int) {
	defer func() {
		t += i // 返回 t + 1 = 3
	}()
	return 2 //t = 2
}

func DeferFunc4() (t int) {
	defer func(i int) {
		fmt.Println(i) //i = 0
		fmt.Println(t) // t =2
	}(t)
	t = 1
	return 2 //t = 2
}

func main() {
	fmt.Println(DeferFunc1(1))
	fmt.Println(DeferFunc2(1))
	fmt.Println(DeferFunc3(1))
	DeferFunc4()
}

输出与解析:

4
1
3
0
2
  1. DeferFunc1 返回 4:命名返回值 t,return t 先把 t=1 赋给返回值,defer 中 t += 3 修改的就是返回值本身,因此最终返回 4。
  2. DeferFunc2 返回 1:匿名返回值,return t 时把局部变量 t 的值 1 拷贝给返回值,之后 defer 修改的只是局部变量 t,不影响已拷贝出去的返回值。
  3. DeferFunc3 返回 3:命名返回值 t,return 2 把 2 赋给 t,defer 中 t += i(i=1)作用于返回值,最终 3。
  4. DeferFunc4 打印 0 和 2:defer 语句的参数在 defer 注册时求值,此时 t 还是零值 0,所以匿名函数里 i=0;而 t 是闭包引用,defer 真正执行时 t 已被 return 2 赋为 2,故打印 2。