Go语言defer的延迟执行机制
1 题目(单选题)
如下Go语言程序的输出结果是()
package main
import "fmt"
func f1(name string) string {
fmt.Println("in f1", name)
return name
}
func f2(name string) string {
fmt.Println("in f2", name)
return name
}
func f3() {
defer f2(f1("function"))
fmt.Println("in f3 function")
}
func main() {
f3()
}
- in f1 function
in f3 function
in f2 function - in f1 function
in f2 function
in f3 function - in f3 function
in f1 function
in f2 function - in f3 function
in f2 function
in f1 function
2 实际答题情况
正确答案 A。
各个选项的选择率为:
A |
20.2% |
B |
0.8% |
C |
78.2% |
D |
0.8% |
3 知识点解读
defer 是 Go 语言中用于延迟执行函数调用的关键字,defer 语句主要包括下面这些知识点:
1) 延迟执行
defer语句会将紧跟其后的函数调用推迟到包含该defer语句的函数返回前执行。
这个特性使得程序员可以在不改变函数逻辑的情况下,确保在函数结束时执行特定操作。
例如:
package main
import "fmt"
func main() {
defer cleanUp()
fmt.Println("hello")
}
func cleanUp() {
fmt.Println("world")
}
运行结果为:
hello
world
2) 执行顺序
如果在一个函数中有多个defer语句,会按照后进先出(LIFO,Last In First Out)的原则执行,即最后声明的defer函数最先被执行,以此类推直至所有的defer函数都被执行完毕。举个例子:
package main
import "fmt"
func add(a, b int) {
fmt.Println("Result =", a + b)
}
func main() {
fmt.Println("Begin")
defer fmt.Println("End")
defer add(36, 64)
defer add(2, 8)
}
运行结果:
Begin
Result = 10
Result = 100
End
3) 与return配合使用
当 defer 与 return 同时存在时,先执行return语句,再执行defer语句。
下面例子演示了defer 与 return 执行的先后顺序:
package main
import "fmt"
func deferFunc() {
fmt.Println("defer func called")
}
func returnFunc() int {
fmt.Println("return func called")
return 0
}
func returnAndDefer() int {
defer deferFunc()
return returnFunc()
}
func main() {
returnAndDefer()
}
输出结果:
return func called
defer func called
可以看到,return 后面的语句先执行,defer 后面的语句后执行
4) 函数参数的值
在 defer 声明时,函数的参数会立即求值并被保存,但实际调用会延迟到包含 defer 语句的函数即将返回时执行。
package main
import "fmt"
func printIndex(index int, _ int) int {
fmt.Print(index)
return index
}
func main() {
defer printIndex(1, printIndex(3, 0))
defer printIndex(2, printIndex(4, 0))
}
这里有 4 次函数调用,index 分别为 1,2,3,4。
那么这 4 次函数的先后执行顺序是什么呢?这里面有两个 defer 语句, 所以 defer 一共会压栈两次:先压 printIndex(1, printIndex(3, 0)),后压 printIndex(2, printIndex(4, 0))。
在压栈 printIndex(1, printIndex(3, 0)) 的时候,需要连同函数地址、函数形参一同进栈,为了得到 printIndex(1, printIndex(3, 0)) 的第二个参数的结果,所以需要先执行printIndex(3, 0)将第二个参数算出,于是 printIndex(3, 0) 就被第一个执行。
同理压栈 printIndex(2, printIndex(4, 0)),需要执行 printIndex(4, 0),于是 printIndex(4, 0) 就被第二个执行。
执行顺序如下:
(1) defer 压栈 printIndex(1, printIndex(3, 0)),压栈函数地址、形参 1、形参 2 (调用 printIndex(3, 0)) –> 打印 3
(2) defer 压栈 printIndex(2, printIndex(4, 0)),压栈函数地址、形参 1、形参 2 (调用 printIndex(4, 0)) –> 打印 4
(3) defer 出栈 printIndex(2, printIndex(4, 0)), 调用 printIndex2 –> 打印 2
(4) defer 出栈 printIndex(1, printIndex(3, 0)), 调用 printIndex1 –> 打印 1
最终输出结果:3421
5) 匿名函数
defer 延迟执行时,如果函数调用中使用了匿名函数或闭包,它们可能会对外部变量产生影响,因为它们保留了对外部变量的引用,例如:
package main
import "fmt"
func returnButDefer() (t int) { // 命名返回值t初始化0, 并且作用域为该函数全域
defer func() {
t = t * 10
}()
return 1
}
func main() {
fmt.Println(returnButDefer())
}
returnButDefer()原本应该返回1,但是在 return 之后,又被 defer 的匿名 func 函数执行,所以 t = t * 10 被执行,最后 returnButDefer() 返回给上层 main() 的结果为 10
输出结果:10
4 题目解析
我们来分析题目中f3函数中defer语句的执行过程:
(1)当f3函数执行到defer语句时,会先执行f1("function")这个函数调用;所以,"in f1 function"会首先被打印出来。
这是因为“在 defer 声明时,函数的参数会立即求值并被保存”。
(2)然后,f1函数会返回name这个字符串(内容为"function")。
(3)接着,defer语句会将f2(f1("function"))这个函数调用延迟到f3函数执行完毕之后再执行。
(4)最后,f3函数会打印出"in f3 function"。然后,defer语句中延迟的函数调用f2(f1("function"))会被执行。最后"in f2 function"会被打印出来。
因此,这段程序的输出结果是:
A.
in f1 function
in f3 function
in f2 function
这个题目涉及到了Go语言中defer语句的执行时机以及函数调用的顺序;通过这个案例,我们可以更好地理解了defer语句的作用和函数调用的执行顺序。
5 总结
defer 是在 Go 语言中确保清理工作执行的一种机制,常用于资源释放,如文件关闭、锁的释放、数据库连接的关闭等。它保证这些清理工作在函数执行结束后执行,有效地预防了因忘记释放资源而导致的问题。
这个特性使得 defer 在资源管理非常有用,通过延迟执行,它确保了资源的及时释放。然而,过度使用 defer 可能增加代码的复杂性和内存消耗,因此需要谨慎使用,避免滥用。
6 推荐学习资料
Go语言官方文档:
https://go.dev/blog/defer-panic-and-recover
https://medium.com/codex/what-do-you-need-to-know-about-golangs-defer-4fac71e0f00b
本文来自博客园,作者:易先讯,转载请注明原文链接:https://www.cnblogs.com/gongxianjin/p/17961278