Golang 中你应该知道的 defer 知识(基础应用篇)
1.1 使用场景
资源清理
例如关闭文件、数据库连接或网络连接等。
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// do something
}
解锁资源
在并发编程中确保锁被解锁、执行 wg.Done
等。
var mu sync.Mutex
func criticalSection() {
mu.Lock()
defer mu.Unlock()
// do something
}
func worker(wg *sync.WaitGroup, id int) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
}
确保函数执行结束后进行后续操作
如记录日志或调试信息、性能分析等。
package main
import (
"fmt"
"time"
)
// 记录函数执行时间
func timeTrack(start time.Time, name string) {
elapsed := time.Since(start)
fmt.Printf("%s took %s\n", name, elapsed)
}
// 记录并跟踪函数的执行
func someFunction() {
defer timeTrack(time.Now(), "someFunction")
defer fmt.Println("Function completed")
fmt.Println("Executing someFunction...")
time.Sleep(2 * time.Second)
// 模拟多个返回点
if true {
return // 即使有 return,defer 仍然会执行
}
fmt.Println("This line won't be reached")
}
func main() {
someFunction()
}
通过 defer
,我们可以极大简化代码的编写和维护,减少资源泄露和逻辑错误的风险。接下来,我们深入了解 defer
的一些核心概念。
1.2 触发时机
defer
语句会在包围它的函数返回之前执行。无论函数怎样退出(正常返回或是 panic
),只要在 return
或 panic
前声明了 defer
,其语句的执行都将得到保证。
func deferFn1() {
defer fmt.Println("defer")
fmt.Println("func")
}
// output:
// func
// defer
func deferFn2() {
defer fmt.Println("defer")
return
}
// output:
// defer
func deferFn3() {
defer fmt.Println("defer")
panic("panic")
}
// output:
// defer
// panic stack
1.3 执行顺序与使用须知
Go 语言中的 defer
语句遵循栈式调用(LIFO - Last In, First Out),即最后一个声明的 defer
最先执行。
func main() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("end...")
}
// output:
// end...
// 3
// 2
// 1
此外,defer
的参数会在声明时立即求值,而不是在延迟函数实际执行时求值。这意味着当 defer
语句执行时,已经保存的参数值将被使用。
func main() {
startedAt := time.Now()
defer func() {
fmt.Println(time.Since(startedAt))
}()
time.Sleep(time.Second)
}
// output: 1s
1.4 返回值处理
在函数有显式 return
语句时,defer
会在返回值被设置之后执行。特别地,如果函数有命名返回参数,defer
可以修改这些返回参数。
func deferReturnFn() int {
a := 1
defer func() {
a = 2
}()
return a
}
// output: 1
在命名返回参数的情况下:
func deferReturnFn() (res int) {
res = 1
defer func() {
res = 2
}()
return
}
// output: 2
匿名函数的返回值会被丢弃:
func deferReturnFn() int {
res := 1
defer func() int {
res++
return res
}()
return res
}
// output: 1
1.5 使用注意点
- 栈式调用:
defer
语句的执行顺序是后进先出。 - 匿名函数:
defer
可以使用闭包捕获外部变量。 - 返回值处理:
defer
可以修改命名返回参数。
此外,要注意 defer
的位置:
// good
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// do something
}
// bad
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
// do something
defer file.Close()
}
1.6 哲学思想
defer
设计背后的哲学思想可以从资源清理的角度理解。传统的资源清理方法通常会使用 goto
或者类似的手段处理多个资源的释放,而 defer
的引入简化了这一过程,让代码更加简洁可读。通过 defer
,Go 语言继承了 RAII 的设计思想,减少了程序员的心智负担。
1.7 总结
本文介绍了 defer
的基础应用,从使用场景、执行顺序到返回值处理等多个方面进行了详细探讨。defer
是一个非常强大的关键字,掌握好它可以让我们写出更加优雅和可靠的 Go 代码。
进一步思考:
defer
的性能如何?Go 团队做了哪些优化?defer
的设计哲学从源码层面是如何实现的?
这些问题值得深入研究,希望在未来能有机会探讨其中的奥秘。