Goの延期体験





0概要

Golangの延期とは何ですか?一般的に言って、それは遅延呼び出しです。Deferは、現在の関数が戻る前に、deferによって登録された関数を実行します。たとえば、defer func_x()を使用すると、関数変数をdeferのグローバルリンクリストに登録し、deferステートメントが終了する関数の前に呼び出すことができます。

Golangを一定期間使用した後、著者がGolangの延期を理解すると、次の2つの効果があります。

  • パニックシーンは引き続き呼び出されます。これは重要な機能であり、通常、コードを簡略化して、シーンに関係なく、defer関数を呼び出す必要があります。通常、ロックまたはリソース解放のシナリオで使用されます。

  • 2つのサポート動作コードを最も近い場所に配置できます。作成と解放、ロックと解放、事前と投稿により、コードが読みやすくなり、優れたプログラミングエクスペリエンスが実現します。最寄りの場所はどこですか?次の行




1延期の特徴

1.1遅延通話

package main

func main() {
    
    
	defer println("--- defer ---")
	println("--- end ---")
}

プログラム出力:

--- end ---
--- defer ---

deferは、main関数が戻る前に呼び出されます。コアポイント:

  • 遅延呼び出し:deferステートメント自体はmainの最初の行ですが、後で出力されます。
  • deferキーワードは関数コンテキスト内にある必要があります。deferは関数内に配置する必要があります

1.2 LIFO

関数に複数の遅延呼び出しがある場合はどうなりますか?プッシュスタック実行、後入れ先出し

package main

import (
    "strconv"
)

func main() {
    
    
	for i := 1; i <= 6; i++ {
    
    
		defer println("defer -->" + strconv.Itoa(i))
	}
	println("--- end ---")
}

スタックへのプッシュ実行。これは、登録された関数が最初に呼び出されることを意味します。上記のように、順次式1、2、3、4、5、6を登録し、最後に「— end —」と出力したため、実行結果は自然に逆になります。プログラムは次のように出力します。

--- end ---
defer -->6
defer -->5
defer -->4
defer -->3
defer -->2
defer -->1

1.3範囲

キーポイント:延期と関数バインディング。2つの理解:

  • Deferは、deferステートメントが配置されている特定の関数にのみバインドされ、スコープはこの関数にのみ含まれます。
  • 構文的には、deferステートメントも関数内にある必要があります。そうでない場合、構文エラーが報告されます。
package main

func main() {
    
    
 func() {
    
    
  defer println("--- defer ---")
 }()
 println("--- ending ---")
}

上記のように、deferは無名関数内にあります。メイン関数に関する限り、無名関数fun(){}()が最初に呼び出されて返され、次にprintln( "— end —")が呼び出されるため、プログラムの出力は当然です:

--- defer ---
--- ending ---

1.4異常なシーン

これは非常に重要な機能です。関数で例外が発生した場合、パニックも実行される可能性があります

Golangは、例外プログラミングモデルを推奨していませんが、パニック回復例外と例外をキャッチするメカニズムも残しています。したがって、延期メカニズムは特に重要であり、それは不可欠であるとさえ言えます。例外を無視して常に呼び出す延期メカニズムがないため、さまざまなリソースリーク、デッドロック、およびその他のシナリオが発生する可能性が非常に高くなります。どうして?パニックが発生するため、プロセスが確実にハングするわけではなく、外層によって回復される可能性があります。

package main

func main() {
    
    
	defer func() {
    
    
		if e := recover(); e != nil {
    
    
			println("--- defer ---")
		}
	}()
 	panic("throw panic")
}

上記のように、メイン関数はdeferを登録し、パニックをアクティブにトリガーします。メイン関数が終了すると、deferによって登録された匿名関数が呼び出されます。もう1つ、ここには実際には2つの主要なポイントがあります。

  • 延期は、異常なパニックシナリオでも呼び出される可能性があります。
  • 意味をなすには、回復を延期と組み合わせる必要があります。




2使用シナリオ

2.1同時同期

次の例では、2つの同時コルーチンで同期制御と通常の操作を実行します。

var wg sync.WaitGroup

for i := 0; i < 2; i++ {
    
    
    wg.Add(1)
    go func() {
    
    
        defer wg.Done()
        // 程序逻辑
    }()
}
wg.Wait()

2.2ロックシーン

ロックとロック解除は一致している必要があります。Golangが延期した後、ロックを書き込んですぐにロック解除を書き込むことができるため、忘れることはありません。

 mu.RLock()
 defer mu.RUnlock()

ただし、lockの下のコードは、関数が終了するまでロックされていることに注意しくださいしたがって、次のコードは簡潔で十分に高速である必要があります。次のロジックが非常に複雑な場合は、ロック解除防止の場所を手動で制御する必要があります。

2.3リソースリリース

一部のリソースは一時的に作成され、スコープはライブ関数にのみ存在し、使い果たされた後に破棄する必要があります。このシナリオは、リリースの延期にも適用されます。作成した次の行を解放します。これは非常に優れたプログラミングエクスペリエンスです。このプログラミング方法により、リソースリークを大幅に回避できます。書いた直後に書いてリリースできるので、二度と忘れることはありません。

// new 一个客户端 client;
cli, err := clientv3.New(clientv3.Config{
    
    Endpoints: endpoints})
if err != nil {
    
    
	log.Fatal(err)
}
// 释放该 client ,也就是说该 client 的声明周期就只在该函数中;
defer cli.Close()

パニック-例外処理の回復

回復は延期と組み合わせる必要があります。姿勢は一般的に次のとおりです。

 defer func() {
    
    
	  if v := recover(); v != nil {
    
    
	   _ = fmt.Errorf("PANIC=%v", v)
	  }
 }()




3延期の落とし穴について

3.1遅延がスタックにプッシュされたときの値のコピーの問題

説明:
1。deferが実行されると、一時的に実行されず、defer後のステートメントが別のスタック(deferスタック)にプッシュされます
。2 関数の実行後、deferスタックから最初の-in-last-outメソッドスタックから
ポップアウトし、3.deferを実行してステートメントをスタックにプッシュします。ステートメントに関連する値もコピーし、スタックにプッシュします。
注:この操作はコピーです。参照ではなく、値の

テストコードは次のとおりです。

func sum(n1 int, n2 int) int {
    
    
	// defer 会在该函数执行完成退出时执行
	defer fmt.Println("defer n1=", n1) // n1 = 10
	defer fmt.Println("defer n2=", n2) // n2 = 20

	n1++           // n1 = 11
	n2++           // n2 = 21
	res := n1 + n2 // res = 32
	fmt.Println("sum n1=", n1)
	fmt.Println("sum n2=", n2)
	return res
}

func main() {
    
    
	_ = sum(10, 20)
}

プログラム出力:

sum n1= 11
sum n2= 21
defer n2= 20
defer n1= 10

経験:
関数の実行前と実行後にプログラムを作成したことがあります。スタック上のdeferによってプッシュされた関数にメッセージをプッシュします。

func(){
    
    
	// 定义 alarmInfoMap, alarmStg, alarmClassMap ....
	defer exitRoutine(alarmInfoMap, alarmStg, alarmClassMap)
	// 给 alarmInfoMap, alarmStg, alarmClassMap 赋值等操作 ....
}

defer関数のpassパラメータはメッセージ本文をプッシュすることであり、結果メッセージは常に空です。デバッグ後、deferが実行される前にメッセージがなく、メッセージ処理がdefer関数の後にあることがわかります。
解決策は、パラメータを渡すときにアドレスの参照を渡すことです。例は次のとおりです。

//退出协程时执行
defer exitRoutine(&alarmInfoMap, &alarmStg, &alarmClassMap)

Go言語の文法とGo言語の一般的な知識のポイントについて詳しく知りたい場合は、私のメモのソースコードを参照してくださいhttps://github.com/qiuyunzhao/go_basis

おすすめ

転載: blog.csdn.net/QiuHaoqian/article/details/106234130