新年的第一天,迎着清晨的阳光,继续构筑我的魅力golang,今天带来的是defer,defer是golang独有的一个特殊的关健字,用于延迟函数的执行。它通常与资源清理、错误处理以及程序退出相关联,是 golang简洁性和可读性的重要体现。通过 defer,开发者可以将需要延迟执行的逻辑嵌入代码中,避免复杂的资源清理代码。
1、defer 的作用
defer 的主要作用是延迟执行一个函数或方法,直到包围它的函数执行结束为止。被延迟的函数会按照后进先出(LIFO,Last In First Out)的顺序执行。
基本语法
defer funcName(args)
当 defer 被调用时,其后的函数会被推入一个延迟栈中,并在当前函数返回时按照后进先出的顺序执行。
基本用法
package main
import "fmt"
func main() {
fmt.Println("Start")
defer fmt.Println("Deferred 1")
defer fmt.Println("Deferred 2")
fmt.Println("End")
}
输出结果:
StartEnd
Deferred 2
Deferred 1
多个 defer 的执行顺序
多个 defer 按照后进先出的顺序执行。上述代码中,Deferred 2 先被推入栈,因此最后执行。
2、defer 的设计思想
defer 的设计思想源于 golang的简洁性和可维护性,目的是提供一种清晰、安全、易用的资源管理方式。其设计理念包括以下几个方面:
2.1 简化资源管理
在许多编程语言中,资源(如文件、网络连接、内存等)的管理通常通过显式的释放操作实现,但容易遗漏,导致资源泄漏。defer 将资源释放的代码直接写在资源分配之后,提高了代码的可读性和健壮性。
示例:文件操作
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("example.txt") // 打开文件
if err != nil { // 如果发生错误,输出错误信息并返回
fmt.Println("Error opening file:", err)
return
}
defer file.Close() // 确保资源在函数结束时被释放
// 文件操作
fmt.Println("File opened successfully")
}
在上述代码中,无论程序是否发生错误,file.Close() 都会被执行,避免资源泄漏。
2.2 提供一致的错误处理
Go 中的错误处理采用显式返回错误的方式。通过 defer,开发者可以在函数结束时统一检查错误或执行恢复逻辑。
示例:错误恢复
package main
import "fmt"
func recoverFromPanic() { //如果捕获到异常,从此处恢复
if r := recover(); r != nil { //判断recover是否捕获到异常
fmt.Println("Recovered from panic:", r)
}
}
func mightPanic() { //可能抛出异常
defer recoverFromPanic() // 捕获异常
panic("Something went wrong!")
}
func main() {
fmt.Println("Starting program")
mightPanic() //抛出异常
fmt.Println("Program continues...")
}
2.3 强调代码简洁性
传统的资源管理通常需要多次显式调用清理代码,而 defer 将清理逻辑集中在资源分配附近,使代码更易于理解和维护。
3、defer 的内部实现
defer 的实现依赖于一个延迟栈(deferred stack)。当调用 defer 时,Go 会将被延迟的函数及其参数存储在栈中,并在函数结束时按照后进先出的顺序执行。
参数的求值时机
被延迟的函数参数会在 defer 声明时立即求值,而非函数执行时。
示例:
package main
import "fmt"
func main() {
x := 10
defer fmt.Println("Deferred value:", x)
x = 20
fmt.Println("Current value:", x)
}
输出结果:
Current value: 20
Deferred value: 10
原因是 defer 声明时,x 的值已经被捕获。
4、defer 的常见应用场景
4.1文件或资源清理
defer 常用于文件、网络连接、数据库连接等资源的清理。
示例:文件清理
package main
import (
"bufio"
"fmt"
"os"
)
func readFile(filename string) { //读取文件函数
file, err := os.Open(filename) //打开文件
if err != nil { //判断文件是否存在
fmt.Println("Error:", err)
return
}
defer file.Close() //延迟关闭文件
scanner := bufio.NewScanner(file) //创建一个扫描文件的对象
for scanner.Scan() { //循环读取文件
fmt.Println(scanner.Text())
}
}
4.2 错误恢复
结合 panic 和 recover,defer 可以实现错误恢复,防止程序崩溃。
示例:错误恢复
package main
import "fmt"
func safeDivision(a, b int) { //创建一个安全除法函数
defer func() { //defer关键字用于延迟执行一个函数
if r := recover(); r != nil { //recover函数用于捕获panic异常
fmt.Println("Recovered from panic:", r)
}
}()
fmt.Println("Result:", a/b)
}
func main() {
safeDivision(10, 0) //调用安全除法函数,传入非法除数0
fmt.Println("Program continues...")
}
输出:
Recovered from panic: runtime error: integer divide by zero
Program continues...
4.3 日志跟踪
defer 可用于记录函数的进入和退出时间,以便调试和性能分析。
示例:日志跟踪
package main
import (
"fmt"
"time"
)
func trace(msg string) func() { //创建一个跟踪msg 相关的函数
start := time.Now() //记录开始时间
fmt.Println("Entering:", msg)
return func() { //返回一个函数
fmt.Println("Exiting:", msg)
fmt.Println("Elapsed time:", time.Since(start))
}
}
func someFunction() {
defer trace("someFunction")() //defer延迟调用
fmt.Println("In someFunction")
time.Sleep(2 * time.Second) //延时2秒
}
func main() {
someFunction()
}
输出结果:
Entering: someFunction
In someFunction
Exiting: someFunction
Elapsed time: 2.0104964s
5、defer 的限制与注意事项
首先是参数求值时机,如前所述,defer 的参数在声明时即被求值,因此后续对参数的修改不会影响被延迟的函数。
然后就是性能开销,defer 有一定的性能开销,尤其是在高频调用中。对于性能敏感的场景,应尽量避免在循环内频繁使用 defer。
优化建议
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
func() {
fmt.Println("Deferred:", i) // 推荐
}()
}
}
defer 设计上强调代码的可读性和资源管理的安全性。尽管 defer 有一些性能限制,但其在绝大多数场景中的实用性远远超过这些限制。合理地使用 defer,可以显著提升代码的清晰度和健壮性。
最后,祝大家新年新气象,期待自己与大家一起探索更多未知,拓宽技术,用代码书写梦想,用努力拥抱可能!