いくつかのGo同時制御方式を知っていますか?

14975 ワード

文書ディレクトリ

  • 引用
  • WaitGroup
  • Channel
  • Context
  • まとめ
  • 文献
  • 引用する


    Golangではgoキーワードでgoroutineを開くことができるので、Goでコンカレントコードを簡単に書くことができます.しかし、これらの同時実行groutinesをどのように効果的に制御しますか?
    同時制御といえば、多くの人が最初に考えたのはロックかもしれません.Golangでは、相互反発ロックsyncを含むロックに関するメカニズムも提供する.Mutexと読み書きロックsync.RWMutex.ロックのほか、原子操作sync/atomicなどもあります.しかし,これらのメカニズムが注目するポイントはgoroutinesの同時データセキュリティである.本稿ではgoroutineの併発を制御として議論する.
    goroutineが制御として発行されるには、WaitGroup、channel、Contextの3つの一般的な方法があります.

    WaitGroup


    WaitGroupはsyncパッケージの下にあり、その使用方法は以下の通りです.
    func main() {
      var wg sync.WaitGroup
    
      wg.Add(2) // 2
    
      go func() {
        wg.Done() // 1
        fmt.Println("goroutine 1  !")
      }()
    
      go func() {
        wg.Done() // 1
        fmt.Println("goroutine 2  !")
      }()
    
      wg.Wait() // 2 
      fmt.Println(" goroutine !")
    }
    
     :
    //goroutine 2  !
    //goroutine 1  !
    // goroutine !
    

    WaitGroupのこのような同時制御方式は特に、あるタスクには複数のgoroutine協同作業が必要であり、各goroutineはそのタスクの一部しかできず、すべてのgoroutineが完了してこそ、タスクが完了する.そのため、WaitGroupは名前の意味と同じように、待つ方法です.
    しかし、実際のビジネスでは、ある要件を満たす場合、goroutineの終了をアクティブに通知するシーンがあります.例えば、バックグラウンドモニタリングgoroutineを開きます.モニタリングが必要でない場合は、このモニタリングgoroutineが終了することを通知する必要があります.そうしないと、空転し続け、漏れを起こします.

    Channel


    上記のシーンではWaitGroupは何もできません.それが考えられる最も簡単な方法は、グローバル変数を定義し、他の場所でこの変数を修正して通知することで、バックグラウンドgoroutineがこの変数をチェックし続け、変数が変化したことを発見したら、自分で閉じることですが、この方法は少し不器用です.この場合、channel+selectが役に立ちます.
    func main() {
      exit := make(chan bool)
    
      go func() {
        for {
          select {
          case exit:
            fmt.Println(" ")
            return
          default:
            fmt.Println(" ")
            time.Sleep(2 * time.Second)
          }
        }
      }()
    
      time.Sleep(5 * time.Second)
      fmt.Println(" ")
      exit  true
    
      // main goroutine 
      time.Sleep(5 * time.Second)
    }// 
    // 
    // 
    // 
    // 
    

    このchannel+selectの組み合わせは、goroutineの終了を優雅に通知する方法です.
    しかし、この案にも限界がある.考えてみて、複数のgoroutineが終了を制御する必要がある場合はどうすればいいですか?もしこれらのgoroutineが他のgoroutineを派生させたら?もちろん,この問題を解決するために多くのchannelを定義することができるが,goroutineの関係チェーンはこのようなシーンの複雑さをもたらす.

    Context


    上記のシーンは、CSスキーマモデルでよく使用されます.Goでは、クライアントごとに個別のgoroutine(A)を開いて一連のrequestを処理することが多く、単一のAでも他のサービス(別のgoroutine Bを起動)を要求することが多く、Bは別のgoroutine Cを要求し、Cはデータbseなどのserverにrequestを送信することもある.クライアントが接続を切断すると、それに関連するA、B、Cは直ちに終了する必要があり、システムはA、B、Cが占有するリソースを回収することができると想定される.脱退Aは簡単ですが、どのようにしてB、Cにも脱退を通知しますか?
    この時、Contextが登場しました.
    func A(ctx context.Context, name string)  {
      go B(ctx ,name) //A B
      for {
        select {
        case ctx.Done():
          fmt.Println(name, "A ")
          return
        default:
          fmt.Println(name, "A do something")
          time.Sleep(2 * time.Second)
        }
      }
    }
    
    func B(ctx context.Context, name string)  {
      for {
        select {
        case ctx.Done():
          fmt.Println(name, "B ")
          return
        default:
          fmt.Println(name, "B do something")
          time.Sleep(2 * time.Second)
        }
      }
    }
    
    func main() {
      ctx, cancel := context.WithCancel(context.Background())
    
      go A(ctx, "【 1】") // client 1 
    
      time.Sleep(3 * time.Second)
      fmt.Println("client , client A,B ")
      cancel() // client , ,ctx.Done() 
    
      time.Sleep(3 * time.Second)
    }//【 1】 A do something
    //【 1】 B do something
    //【 1】 A do something
    //【 1】 B do something
    //client , client A,B 
    //【 1】 B 
    //【 1】 A 
    

    例では、クライアントからの接続要求をシミュレートし、Goroutine Aをオンにして処理し、AはB処理を同時にオンにし、AとBはContextを使用して追跡し、cancel関数を使用してキャンセルを通知すると、この2つのgoroutineは終了します.
    これがContextの制御能力であり、コントローラのようにスイッチを押すと、このContextまたは派生したサブContextに基づいて通知が届く.この場合、クリーンアップ操作を行い、最終的にgoroutineを解放することができ、goroutineの起動後に制御できない問題を優雅に解決することができる.
    Contextの詳細な使い方については、本論文の範囲外です.Contextパッケージについての説明文が続きますので、ご注目ください.

    まとめ


    本論文では,3種類のGolangにおける同時挙動制御モードを列挙した.パターンの間には善し悪しの区別はなく、異なるシーンで適切な案を使うことにある.実際のプロジェクトでは、多くの方法で混合して使用されます.
  • WaitGroup:複数のgoroutineのタスク処理に依存または結合関係がある.
  • channel+select:goroutineを自発的にキャンセルできます.マルチgroutineでのデータ転送;channelはWaitGroupの代わりに動作することができるが、コード論理の複雑さを増加させる.マルチチャネルはContextの機能を満たすことができ,同様にコードロジックを複雑にする.
  • Context:マルチレベルgroutine間の信号伝播(メタデータ伝播、信号伝播のキャンセル、タイムアウト制御などを含む).

  • 文献


    https://mp.weixin.qq.com/s/tloEYzrnKNrrAo1YKdeyrw