39

defer順序に依存する複数のステートメントを発行すること、またはロジックをパッケージ化する無名関数を延期することは、より安全で慣用的ですか?

例:

defer os.Remove(tempFile.Name())
defer tempFile.Close()

上記の場合、構文は最小限ですが、遅延の順序は実行されるロジックとは逆です。

以下のケースでは、より多くの行、より多くの「構文」がありますが、ロジックはより自然な順序になっています。

defer func() {
    tempFile.Close()
    os.Remove(tempFile.Name())
}()

どちらを使用しますか?

4

2 に答える 2

36

この例では、特にエラー処理を追加すると、無名関数が読みやすくなります。

f, err := ioutil.TempFile("", "prefix")
if err != nil {
  log.Println("creating temp file:", err)
  return
}
defer func() {
  err := f.Close()
  if err != nil {
    log.Println("close:", err)
  }
  err = os.Remove(f.Name())
  if err != nil {
    log.Println("remove:", err)
  }
}()

複数のリソースがある場合、defer通常は複数の が適切です。

于 2015-09-12T19:10:57.157 に答える
10

ロスライトの回答述べているように:

複数のリソースがある場合は、通常、複数の defer が適切です。

2019 年 4 月: ただし、その場合は、Go 1.13 (2019 年第 4 四半期) を検討してください。go issue 14939: "runtime: defer is slow"およびgo issue 6980: "cmd/compile: allocate some defer in stack frames "

Go CL 171758を参照してください: "cmd/compile,runtime: スタックに defer レコードを割り当てる"

関数本体で defer が最大 1 回実行される場合、その defer レコードをヒープではなくスタックに割り当てることができます。

これにより、このような遅延 (非常に一般的) が高速になります。

この最適化は、cmd/go バイナリの 370 の静的遅延サイトのうち 363 に適用されます。

name     old time/op  new time/op  delta
Defer-4  52.2ns ± 5%  36.2ns ± 3%  -30.70%  (p=0.000 n=10+10)

2019 年 10 月 (数週間前に Go 1.13 がリリースされました)

これはCL 190098確認されています (Brad Fitzpatrick) :

繰延明細書の費用 [ go test -run NONE -bench BenchmarkDefer$ runtime]

With normal (stack-allocated) defers only:         35.4  ns/op
With open-coded defers:                             5.6  ns/op
Cost of function call alone (remove defer keyword): 4.4  ns/op

しかし、ダミアン・グリスキーは次のように付け加えています

Defer は安くなりますが、panic/recover はより高くつきます。

Cost of defer: 34ns -> 6ns.
Cost of panic/recover: 62ns -> 255ns

それは悪いトレードオフではありません。


言い換えれば、複数の defer を使用することは慣用的である可能性がありますが、Go 1.13+ ではもはや問題にならないパフォーマンス コストによって、その慣行は抑制されました。
( Paschalisのブログ投稿 " What is a defer? And how many can you run? " で説明されているように)

これにより、if defer (コード フローに関係なく関数呼び出しを実行する必要がある場所) が可能になります。

ただし、John Refior氏は、これdeferは同期的であると述べています。

実際には defer は関数が終了する直前に実行されます。
また、同期的に発生するため、呼び出し元は defer が完了するまで待機します。

したがって、複数の defer を使用できるようになったとしても、それらが高速であることを確認するか、John が指摘するように:

幸いなことに、ゴルーチンを a でラップするのは簡単でdefer、呼び出し元を遅らせることなく、必要なフロー制御とタイミングを提供します。

func Handler(w http.ResponseWriter, r *http.Request) {
    log.Println("Entered Handler")
    defer func() {
        go func() {
            time.Sleep(5 * time.Second)
            log.Println("Exiting goroutine")
        }()
        log.Println("Exiting defer")
    }()
}

多くの場合、遅延はミューテックスのロック、接続またはファイル記述子のクローズに使用され、それらが行う作業は高速であるか、呼び出し元が移動する前に完了したい.

ただし、クライアントが HTTP ハンドラーの最後で待機する必要のない遅い作業を行っている場合は、呼び出しを非同期にすることで、ユーザー エクスペリエンスを大幅に向上させることができます。

于 2019-04-27T06:48:50.173 に答える