关键字defer的使用类似于面向对象编程语言Java和C#中的finally语句块。通常用于释放一些分配的资源。典型的例子是解锁互斥体或关闭文件。
多个延迟执行语句的处理顺序
如果注册了多个延迟操作,它们将以相反的顺序执行(类似于堆栈,即后进先出)。以下代码按顺序延迟一系列数字打印语句,如下所示。
package main
import (
"fmt"
)
func main() {
fmt.Println("defer 開始")
// deferを遅延呼び出しスタックに入れる
defer fmt.Println(1)
defer fmt.Println(2)
// 最後に入れたものを一番上にする。先に呼ばれる。
defer fmt.Println(3)
fmt.Println("defer 終了")
} 这是代码输出:
defer begin
defer end
3
2
1
结果分析如下。
- 代码的延迟顺序与最终执行顺序相反。
- 延迟调用在放置延迟的函数末尾执行。当功能恢复正常或发生停机时,就会发生功能终止。
使用延迟执行语句在函数退出时释放资源
处理业务和逻辑上的成对操作,例如打开和关闭文件、接收和响应请求、锁定和解锁等,可能会很繁琐。这些操作中最容易被忽视的是正确释放资源并在每个函数结束时关闭。
由于defer语句恰好在函数退出时执行,因此使用defer可以非常方便地处理资源释放。
1)使用延迟同时解锁
在以下示例中,映射在函数内同时使用。为了防止竞争条件,请使用sync.Mutex 进行锁定。请参阅下面的代码。
// 変数
var (
// デモ用のマップ
valueByKey = make(map[string]int)
// マップを安全に同時に使用するためのミューテックス
valueByKeyGuard sync.Mutex
)
// キーに対応する値を読み取る
func readValue(key string) int {
// 共有リソースに対してロックをかける
valueByKeyGuard.Lock()
// 値を読み取る
v := valueByKey[key]
// 共有リソースのロックを解放する
valueByKeyGuard.Unlock()
// 値を返す
return v
} 代码说明如下:
- 第三行实例化map,键为string类型,值为int。
- 第 5 行,默认情况下地图不是同时安全的。 sync.Mutex 准备互斥锁以保护映射访问。
- 在第 9 行中,readValue() 函数被赋予键,并在从映射中获取值后返回。该函数是在并发环境下使用的,所以必须保证并发安全。
- 第 11 行使用互斥锁进行锁定。
- 第 13 行,从地图中获取值。
- 第 15 行使用互斥锁来释放锁。
- 第17行返回获取的map值。
使用 defer 语句简化上述语句。请参阅下面的代码。
func readValue(key string) int {
valueByKeyGuard.Lock()
// defer後面の言葉はすぐに実行されず、関数の終了時に実行されます。
defer valueByKeyGuard.Unlock()
return valueByKey[key]
} 上述代码第6行至第8行是对之前代码的修改和补充,代码说明如下。
- 第 6 行锁定互斥体,然后使用 defer 语句将其解锁。该语句不会立即执行,而是在 readValue() 函数返回时执行。
- 第8行是从map中查询并返回值的过程,与不使用互斥体的情况下编写是一样的,但比上面的代码简单。
2)使用延迟释放文件句柄
对文件的操作必须经过几个过程:打开文件、获取并操作文件资源、关闭资源,如果操作完成后不关闭文件资源,则进程无法释放文件。文件资源。在下面的示例中,根据函数的文件名检索文件,函数必须打开文件、检索文件大小并关闭文件,因为系统操作的每个步骤都需要错误处理。并且每一步处理都有可能触发终止,因此终止时必须释放资源。另外,您必须非常小心,在函数结束时正确释放文件资源。请参阅下面的代码。
// ファイル名からファイルサイズを取得する関数
func fileSize(filename string) int64 {
// ファイル名を指定してファイルハンドルを取得し、エラーを返す
f, err := os.Open(filename)
// エラーが発生した場合、ファイルサイズ0を返す
if err != nil {
return 0
}
// ファイルのステータス情報を取得する
info, err := f.Stat()
// 情報の取得に失敗した場合、ファイルを閉じてファイルサイズ0を返す
if err != nil {
f.Close()
return 0
}
// ファイルサイズを取得する
size := info.Size()
// ファイルを閉じる
f.Close()
// ファイルサイズを返す
return size
} 代码说明如下:
- 第二行定义了一个获取文件大小的函数,返回值是一个64位的文件大小值。
- 第5行使用os包提供的函数Open()根据指定的文件名打开文件,并返回用于操作文件的句柄以及操作中的任何错误。
- 第8行,如果打开过程中发生错误,例如找不到文件或文件被占用,则返回文件大小为0。
- 第13行,文件句柄f此时可以正常使用,但是使用f的方法Stat()检索文件信息,但检索信息时可能会出现错误。
- 第 16-19 行处理错误。至此,文件打开成功。要释放资源,必须通过调用 f 的 Close() 方法关闭文件。否则会发生资源泄漏。
- 第 22 行获取文件大小。
- 第 25 行关闭文件并释放资源。
- 第28行返回获取的文件大小。
上例中,第25行是关闭文件的操作。下面使用Defer来简化代码。这是代码:
func fileSize(filename string) int64 {
f, err := os.Open(filename)
if err != nil {
return 0
}
// 延期にCloseを呼び出し、この時Closeは呼び出されない
defer f.Close()
info, err := f.Stat()
if err != nil {
// 延期機構がトリガーされて、Closeがファイルを閉じる
return 0
}
size := info.Size()
// 延期機構がトリガーされて、Closeがファイルを閉じる
return size
} 代码中加粗部分是与之前代码相比发生变化的部分,代码说明如下:
- 第 10 行使用 defer 来延迟在文件成功打开后调用 f.Close()。请注意,此代码语句不能放在第四个空行上。如果文件未正确打开,则在延迟后触发 f 语句时将触发崩溃错误。
- 第 16 行和第 22 行,在函数返回之前调用 defer (f.Close()) 之后的语句,并自动释放资源。




![2021 年如何设置 Raspberry Pi Web 服务器 [指南]](https://i0.wp.com/pcmanabu.com/wp-content/uploads/2019/10/web-server-02-309x198.png?w=1200&resize=1200,0&ssl=1)

