1 defer语句
该语句用于延迟调用指定的函数,它只能出现在函数或方法的内部,由defer 关键字以及针对某个函数的调用表达式组成。这里被调用的函数称为延迟函数。简单的示例如下
func outerFunc() {
defer fmt.Println("函数执行结束前一刻才会被打印")
fmt.Println("第一个被打印")
}
《代码说明》defer关键字后面是针对fmt.Println()函数的调用表达式。这里的outerFunc()称为外围函数。
defer语句经常用于处理成对的操作,如打开和关闭、连接和断开连接、加锁和释放锁等。通过defer 机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放和回收。释放资源的defer应该直接跟在请求资源的语句后。
<注意> defer后面必须是函数或方法的调用,不能是普通语句,否则会报 "expression in defer must be function call" 错误。
2 defer语句的特性
- 当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正结束执行。
- 当执行外围函数的return语句时,只有其中所有的延迟函数都执行完毕后,外围函数才会真正返回函数返回值。
- 当外围函数中的代码引发运行时恐慌时(即执行了panic语句),只有其中所有的延迟函数都执行完毕后,该运行时恐慌才会真正扩散至调用函数。
正因为defer有这样的特性,所以它成为了执行释放资源或异常处理等收尾任务的首选。它有两个明显优势:
- 对延迟函数的调用总会在外围函数执行结束前执行。
- defer语句在外围函数体中的的位置不限,并且数量不限。
3 defer执行时机
在Go语言中,return语句在底层并不是原子操作,它分为给返回值赋值和执行RET指令(汇编指令)两步。而defer语句执行时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:
<说明> 可以看到,Go语言的return语句并不是RET汇编指令,它有两个步骤:1. 先更新返回值;2.再执行RET指令。
[参考] CALL和RET指令---汇编学习笔记
示例:defer经典案例。
func f1() int {
x := 5
defer func() {
x++ //修改的是变量x的值,不是返回值
}()
return x //1.返回值赋值,即将x=5赋值给一个中间变量 2.执行defer语句 3.执行真正的R