基本概念
defer 是 Go 语言中的一个关键字,用于延迟(推迟)一个函数或方法的执行,直到包含它的函数执行完毕时才会被调用,无论包含它的函数是通过 return 正常结束还是由于 panic 导致的异常结束。
defer 语句通常用于资源的释放、解锁以及确保某些关键操作的完成。
参数求值与陷阱
在 Go 中,defer 语句中的函数参数会在 defer 语句执行时立即求值。这意味着,无论 defer 语句之后的代码如何改变变量的值,defer 调用的函数将使用 defer 语句执行时的参数值。
package main
import "fmt"
func printValue(v int) {
fmt.Println("print_value on defer: ", v)
}
func TestDeferArg() {
i := 10
fmt.Println("print_value before defer: ", i) // 输出: print_value before defer: 10
i++
defer printValue(i * 10) // 参数在此时求值,即 11 * 10 = 110
i++
fmt.Println("print_value after defer: ", i) // 输出: print_value after defer: 12
}
func main() {
TestDeferArg()
// 输出: print_value on defer: 110
}
解释
参数预计算:defer 语句定义时即计算并固定参数值。
具体来说,在把 defer 压入“栈”时,会同时压入函数地址和函数形参,也就是会在这个时候就把参数先算好。所以在执行到第 3 行代码的时候,就会把 i*10 算好,然后同 printValue 一同压入到延迟执行栈中。
环境变量捕获
当 defer 语句后面跟的是一个匿名函数时,这个匿名函数可以捕获其外部作用域中的变量。这意味着,即使外部函数的执行已经结束,匿名函数仍然可以访问和操作这些变量。
package main
import "fmt"
func printValue(v int) {
fmt.Println("print_value in defer: ", v)
}
func TestDeferNoArg() {
i := 10
fmt.Println("print_value before defer: ", i) // 输出: print_value before defer: 10
i++
defer func() {
printValue(i * 10) // 匿名函数捕获了 i 的当前值
}()
i++
fmt.Println("print_value after defer: ", i) // 输出: print_value after defer: 12
}
func main() {
TestDeferNoArg()
// 输出: print_value in defer: 120
}
解释
这个时候其实没有参数,所以会直接将下面闭包压入延迟栈中。 而闭包是可以捕获环境变量的,所以在 TestDeferNoArg return 后,defer 可以捕获到 i 的值,为更新后的 i++,最后再进行 printValue(i * 10)。
使用场景
defer 语句在 Go 语言中有许多实际应用场景,例如:
- 资源释放:确保文件、数据库连接等资源在使用后被正确关闭。
- 解锁互斥锁:在并发编程中,确保互斥锁在函数退出前被释放。
- 错误处理:在函数执行过程中,如果发生错误,可以使用 defer 来执行一些清理工作。
注意事项
- defer 语句的执行顺序是后进先出(LIFO),即最后一个 defer 语句会最先执行。
- defer 语句不能保证立即执行,它会在包含它的函数即将返回之前执行。
- 过度使用 defer 可能会导致性能问题,因为每个 defer 语句都会增加程序的调用栈深度。