Go 言語の defer (遅延実行ステートメント)

Go 言語の defer (遅延実行ステートメント)

 
 
Go 言語の defer 文は後続の文の処理を遅延させ、defer が属する関数が戻ろうとしたときに、遅延した処理文が defer の逆の順序、つまり defer になっている文を実行します。 first will run last. defer last のステートメントが最初に実行されます。

 

キーワード 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()) は、関数が戻ってリソースを自動的に解放する前に呼び出されます。
 

「 Go 言語の defer (遅延実行ステートメント)」についてわかりやすく解説!絶対に観るべきベスト2動画

defer + recover を使用して Go でパニックを処理する
17 Golang Tutorial – Defer and File System