defer
Go 中的 defer 详解
在 Go 语言中,defer 用于在函数返回之前执行延迟操作,典型用途是资源释放和清理。
基本用法
defer 语句注册一个函数调用,这个函数会在包含 defer 语句的函数执行完毕后执行。defer 的语法如下:
func functionName() {
defer deferredFunction()
// 函数体
}示例:基本用法
package main
import "fmt"
func deferExample() (result int) {
result = 1
defer func() {
fmt.Println("Deferred function called")
result = 2
}()
return
}
func multipleDefers() {
defer fmt.Println("First deferred")
defer fmt.Println("Second deferred")
fmt.Println("Function body")
}
func main() {
fmt.Println("Starting deferExample:")
fmt.Println("Return value:", deferExample())
fmt.Println("\nStarting multipleDefers:")
multipleDefers()
}输出:
Starting deferExample:
Deferred function called
Return value: 2
Starting multipleDefers:
Function body
Second deferred
First deferred多个 defer 的触发顺序
多个 defer 语句按照后进先出(LIFO)的顺序执行。这意味着最后一个注册的 defer 语句最先执行。
示例:多个 defer 语句
package main
import "fmt"
func multipleDefers() {
defer fmt.Println("First deferred")
defer fmt.Println("Second deferred")
fmt.Println("Function body")
}
func main() {
multipleDefers()
}输出:
Function body
Second deferred
First deferreddefer 与 return 的关系
defer语句在return执行之后,但在函数真正返回之前执行。defer语句可以访问函数的返回值,并且可以修改它们。
示例:defer 与 return 的关系
package main
import "fmt"
func modifyReturnValue() (result int) {
result = 1
defer func() {
fmt.Println("Deferred function modifies result")
result = 2
}()
return
}
func main() {
fmt.Println("Return value:", modifyReturnValue())
}输出:
Deferred function modifies result
Return value: 2defer 与 for
在 for 循环里用 defer,主要注意注册次数和执行顺序这两点;Go 1.14 之后 defer 本身的开销也降了不少。
defer 在 for 循环中的使用注意事项
1. 每次循环迭代都会注册一个新的 defer
每次迭代都会注册一个新的 defer,这些调用要等到函数返回才执行;循环次数多时,注册的数据结构一直占着,执行也被攒到最后一刻。
2. defer 语句的执行顺序
后注册的先执行。多个资源一起申请时,释放顺序要按这条规则核对。
示例:在 for 循环中使用 defer
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer fmt.Printf("Deferred: %d\n", i)
}
}输出:
Deferred: 2
Deferred: 1
Deferred: 0在这个示例中,尽管 defer 语句在循环的每次迭代中都被注册,但它们的执行顺序是相反的,这体现了 defer 语句的 LIFO 特性。
Go 的改进
Go 的 defer 机制在版本间有过几次改进,主要是性能。
1. Go 1.14 及以后的性能优化
Go 1.14 起,defer 的注册和执行开销都降了,栈管理也更省,高频调用 defer 的代码最先受益。
2. 堆栈分配优化
在 Go 1.14 版本之前,defer 的开销相对较大,因为每个 defer 语句都会在栈上分配一个新的数据结构。Go 1.14 引入了更高效的堆栈分配策略,以减少 defer 的开销,尤其是在循环和高并发场景中。
示例:性能改进
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
for i := 0; i < 1000000; i++ {
defer fmt.Println("Deferred:", i)
}
fmt.Println("Time taken:", time.Since(start))
}在 Go 1.14 及以后的版本中,上述示例的执行时间会明显低于早期版本,这反映了 defer 性能优化的效果。
总结
- 基本用法:
defer在函数返回之前执行,用于资源清理。 for循环中的使用:每次循环迭代都会注册一个新的defer,执行顺序按 LIFO 规则。- Go 的改进:Go 1.14 及以后版本优化了
defer的性能,减少了开销,并改进了堆栈管理。
defer 的底层实现
defer 的底层通过栈结构实现。每当遇到 defer 语句时,Go 会将待执行的函数调用和参数压入栈中。在函数返回之前,Go 会按 LIFO 顺序依次弹出栈中的函数调用并执行。
汇编示例
为了理解底层实现,可以查看 Go 的汇编代码。以下是一个简单的汇编代码片段,展示了 defer 的处理(部分简化):
TEXT main.deferredExample(SB), NOSPLIT, $0
MOVQ $1, result(SP)
// 设置 deferred function
MOVQ $deferFunction(SB), R8
MOVQ R8, deferCall(SP)
RET
TEXT main.multipleDefers(SB), NOSPLIT, $0
// 设置第一个 deferred function
MOVQ $firstDeferred(SB), R8
MOVQ R8, deferCall1(SP)
// 设置第二个 deferred function
MOVQ $secondDeferred(SB), R8
MOVQ R8, deferCall2(SP)
RET
TEXT deferFunction(SB), NOSPLIT, $0
// 执行 deferred function
RET
TEXT firstDeferred(SB), NOSPLIT, $0
// 执行第一个 deferred function
RET
TEXT secondDeferred(SB), NOSPLIT, $0
// 执行第二个 deferred function
RET总结
- 基本用法:
defer用于在函数返回之前执行清理操作。 - 多个
defer语句:按照后进先出(LIFO)顺序执行。 - 与
return的关系:defer语句在return执行后但在函数真正返回之前执行,可以修改返回值。 - 底层实现:
defer使用栈结构来管理函数调用,确保按 LIFO 顺序执行。
参考资料
- 语言规范 · 函数声明
- 语言规范 · 调用
- Effective Go · 函数
- 语言规范 · Defer 语句
- 官方博客 · Defer, Panic, and Recover
- Effective Go · Defer