defer用法及解释

384 阅读4分钟

defer 语句是 Go 语言中一个非常有用的特性,它用于在函数退出时执行一段代码,无论是正常返回还是发生异常。通常,defer 语句用于释放资源(例如关闭文件、解锁互斥锁等),确保在函数执行完毕后某些清理操作一定会被执行。

基本语法:

defer function_name()

或者:

defer expression

defer 的工作原理

  1. 延迟执行defer 语句会将指定的函数调用或表达式的执行延迟到封闭函数(即当前函数)执行完毕后。
  2. 栈式执行:如果在一个函数中有多个 defer 语句,它们会按 后进先出(LIFO) 的顺序执行。也就是说,最后定义的 defer 最先执行。

defer 的常见应用场景

1. 用于释放资源(文件、数据库连接等)

例如,打开文件后你希望在函数结束时自动关闭文件:

package main

import (
	"fmt"
	"os"
)

func main() {
	// 打开文件
	file, err := os.Open("example.txt")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	// 延迟关闭文件
	defer file.Close()

	// 使用文件执行其他操作
	fmt.Println("File opened successfully")

	// 这里 file.Close() 会在 main 函数退出时自动被调用
}

在这个例子中,defer file.Close() 确保文件会在 main 函数结束时被关闭。无论 main 函数的执行过程中是否发生错误,file.Close() 都会被调用。

2. 用于解锁互斥锁(mutex)

当你在并发程序中使用互斥锁时,defer 语句可以确保锁在函数结束时被释放:

package main

import (
	"fmt"
	"sync"
)

var mutex sync.Mutex

func criticalSection() {
	// 使用 defer 解锁互斥锁
	mutex.Lock()
	defer mutex.Unlock()

	// 这里是临界区代码
	fmt.Println("Critical section")
}

func main() {
	criticalSection()
}

在此例中,defer mutex.Unlock() 保证了 mutexcriticalSection 函数执行完毕后被解锁。即使函数执行过程中发生了 panic,defer 语句依然会确保锁被释放。

3. 捕获 panic

defer 还可以与 recover 配合使用,用于捕获 panic 并进行适当的恢复,避免程序崩溃:

package main

import "fmt"

func safeDivision(a, b int) (result int) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered from panic:", r)
			result = 0 // 如果发生 panic,返回 0 作为结果
		}
	}()

	// 如果 b 是 0,触发 panic
	return a / b
}

func main() {
	fmt.Println(safeDivision(10, 0)) // 发生 panic, 被 recover 捕获
}

这里,defer 语句与 recover 配合使用,在 safeDivision 函数发生 panic 时捕获并处理,避免程序崩溃。

defer 的执行顺序

Go 中的 defer 语句会按 后进先出(LIFO) 的顺序执行。这意味着,如果你在函数中使用多个 defer 语句,它们会按照调用顺序的相反顺序执行。

示例:

package main

import "fmt"

func main() {
	defer fmt.Println("First")
	defer fmt.Println("Second")
	defer fmt.Println("Third")

	fmt.Println("In main function")
}

输出:

In main function
Third
Second
First

解释:虽然 defer 语句在函数中是按照顺序书写的,但它们会按照“后进先出”的顺序执行。Third 最先被执行,First 最后执行。

defer 语句中的参数

当你使用 defer 时,传递给 defer 的参数会在 defer 语句执行时就被计算,而不是在 defer 语句实际执行时。

示例:

package main

import "fmt"

func main() {
	x := 10
	defer fmt.Println("Deferred value:", x)
	x = 20
}

输出:

Deferred value: 10

解释:虽然在 defer 语句之后修改了 x 的值,但 defer 语句中的 x 参数在 defer 被定义时就已经被计算并保存为 10,因此打印出的值是 10

defer 性能影响

虽然 defer 语句非常方便,但它会带来一些性能开销,尤其是在高频调用的场景中。具体开销主要体现在:

  • 延迟执行的管理:Go 需要管理每个 defer 语句的执行(存储、排序等)。
  • 栈的使用:Go 使用栈来保存所有待执行的 defer 语句,栈的操作也会引入一些性能开销。

然而,除非在高频调用的性能关键部分使用,通常 defer 的性能开销是可以接受的。

总结

  • defer 是 Go 语言中一个非常有用的功能,常用于确保在函数执行结束时做清理工作,如关闭文件、解锁互斥锁等。
  • defer 语句会延迟执行,且按照 后进先出(LIFO) 的顺序执行。
  • defer 语句中的参数会在定义时计算,而不是执行时计算。
  • defer 可以与 recover 配合使用,用于捕获并处理 panic。

尽管 defer 提供了强大的功能,使用时仍需要注意其潜在的性能开销,特别是在性能敏感的代码中。