キーワード defer の使用法は、オブジェクト指向プログラミング言語JavaおよびC#のfinally ステートメント ブロックに似ています。一般的に、割り当てられたリソースの一部を解放するために使用されます。典型的な例は、ミューテックスのロックを解除したり、ファイルを閉じたりすることです。
複数の遅延実行ステートメントの処理順序
複数の遅延動作が登録されている場合、それらは逆の順序で実行されます (スタックと同様、つまり、後入れ先出し)。次のコードは、以下に示すように、一連の数値 print ステートメントを順番に遅延させます。
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 ステートメントは関数が終了するときにたまたま実行されるため、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
}
コードの説明は次のとおりです。
- 3 行目ではマップがインスタンス化され、キーは文字列型、値は int です。
- 5 行目、マップはデフォルトでは同時安全ではありません。マップ アクセスを保護するために sync.Mutex ミューテックスを準備します。
- 9 行目では、readValue() 関数にキーが与えられ、マップから値を取得した後に戻ります。この関数は同時環境で使用されるため、同時実行の安全性を確保する必要があります。
- 11 行目、ミューテックスを使用してロックします。
- 13 行目、マップから値を取得します。
- 15 行目で、ミューテックスを使用してロックを解除します。
- 17行目は取得したマップ値を返します。
defer ステートメントを使用して上記のステートメントを簡素化します。次のコードを参照してください。
func readValue(key string) int {
valueByKeyGuard.Lock()
// defer後面の言葉はすぐに実行されず、関数の終了時に実行されます。
defer valueByKeyGuard.Unlock()
return valueByKey[key]
}
上記コードの 6 行目から 8 行目は、以前のコードを修正および追加したコードであり、コードの説明は次のとおりです。
- 6 行目では、ミューテックスがロックされた後、defer ステートメントを使用してロックを解除します。このステートメントはすぐには実行されませんが、readValue() 関数が返されたときに実行されます。
- 8行目はマップから値を問い合わせて返す処理ですが、ミューテックスを使わない場合の書き方と同じですが、上記のコードに比べてこちらの方が簡単です。
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
}
コードの説明は次のとおりです。
- 2 行目はファイル サイズを取得する関数を定義しており、戻り値は 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() の呼び出しを遅らせます。このコード文を 4 行目の空白行に置くことはできないことに注意してください。ファイルが正しく開かれないと、f は遅延後、ステートメントが起動すると、クラッシュ エラーがトリガーされます。
- 16 行目と 22 行目、defer の後のステートメント (f.Close()) は、関数が戻ってリソースを自動的に解放する前に呼び出されます。