ホームページ >よくある問題 >Go言語で同時実行制御を実装する方法

Go言語で同時実行制御を実装する方法

DDD
DDDオリジナル
2023-06-08 14:20:471870ブラウズ

Go 言語で同時実行制御を実装する方法: 1. WaitGroup、複数のゴルーチンのタスク処理に依存関係または結合関係があります; 2. チャネル、ゴルーチンをアクティブにキャンセルでき、複数のゴルーチンでデータを転送できます。 WaitGroup の作業、Context の機能との出会い、3. コンテキスト、メタデータ伝播、信号伝播のキャンセル、タイムアウト制御などを含む、マルチレベル グルーチン間の信号伝播。

Go言語で同時実行制御を実装する方法

#この記事の動作環境: Windows 10 システム、go1.20 バージョン、dell g3 コンピューター。

Golang では、go キーワードを使用して goroutine を開くことができるため、Go で同時実行コードを簡単に作成できます。しかし、これらの同時実行グルーチンを効果的に制御するにはどうすればよいでしょうか?

同時実行制御というと、多くの人は最初にロックを思い浮かべるかもしれません。 Golang は、ミューテックス ロック sync.Mutex や読み取り/書き込みロック sync.RWMutex などのロック関連のメカニズムも提供します。ロックに加えて、同期/アトミックなどのアトミック操作もあります。ただし、これらのメカニズムの焦点は、ゴルーチンの同時データの安全性にあります。この記事で議論したいのは、ゴルーチンの同時実行動作制御です。

ゴルーチンの同時動作制御には、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 はその名前と同じ意味で、待機方法です。

しかし、実際のビジネスでは、特定の要件が満たされた場合、特定のゴルーチンの終了を能動的に通知する必要があるというシナリオがあります。たとえば、バックグラウンド監視ゴルーチンを開始した場合、監視が必要なくなったら、監視ゴルーチンに終了するように通知する必要があります。そうしないと、アイドル状態が続き、リークが発生します。

Channel

上記のシナリオでは、WaitGroup は何もできません。考えられる最も単純な方法: グローバル変数を定義し、この変数を他の場所で変更することで通知する バックグラウンドのゴルーチンは常にこの変数をチェックし、変数が変更されたことが判明した場合は、自動的に閉じます。方法はやや面倒で不器用です。この場合、チャンネル選択が役に立ちます。

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)}输出:
//监控中
//监控中
//监控中
//通知监控退出
//退出监控

このチャネル選択の組み合わせは、ゴルーチンに終了を通知するより洗練された方法です。

ただし、このソリューションにも限界があります。想像してみてください。複数の goroutine があり、それらをすべて終了するために制御する必要がある場合はどうなるでしょうか?これらのゴルーチンが他のゴルーチンを生成したらどうなるでしょうか?もちろん、この問題を解決するために多くのチャネルを定義できますが、ゴルーチンの関係チェーンにより、このシナリオは複雑になります。

コンテキスト

上記のシナリオは、CS アーキテクチャ モデルでは一般的です。 Go では、一連のリクエストを処理するためにクライアントごとに個別のゴルーチン (A) が開かれることが多く、多くの場合、単一の A が他のサービスもリクエストし (別のゴルーチン B を開始する)、B が別のゴルーチンをリクエストすることもあります。次に、リクエストをデータベースなどのサーバーに送信します。クライアントが切断されたとき、それに関連付けられている 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退出

この例では、クライアントからの接続リクエストがシミュレートされ、それに応じてゴルーチン A が開かれて処理されます。A は B の処理も有効にします。A と B は両方とも追跡に Context を使用します。cancel 関数を使用する場合キャンセルを通知するには、これら 2 つのゴルーチンが終了されます。

これは、コンテキストの制御機能です。コントローラーのようなものです。スイッチを押すと、このコンテキストに基づく、またはコンテキストから派生したすべてのサブコンテキストが通知を受け取ります。このとき、クリーンアップ操作を実行できます。最後に、起動後に制御できない goroutine の問題をエレガントに解決する goroutine をリリースします。

Context の詳しい使用方法については、この記事の範囲を超えています。 Context パッケージについて詳しく説明するフォローアップ記事がある予定ですので、楽しみにお待ちください。

概要

この記事では、Golang の 3 つの同時実行動作制御モードをリストします。モード間に良いか悪いかの区別はなく、異なるシナリオに適切なソリューションを使用するかどうかに依存します。実際のプロジェクトでは、複数の手法を組み合わせて使用​​することがよくあります。

  • WaitGroup: 複数のゴルーチンのタスク処理間に依存関係または結合関係があります。
  • チャネル選択: goroutine をアクティブにキャンセルできます; 複数の groutine でのデータ転送; チャネルは WaitGroup の作業を置き換えることができますが、コード ロジックの複雑さが増加します; 複数のチャネルで Context の機能を満たすことができ、同様に、コードロジックも複雑になります。
  • コンテキスト: マルチレベル グルーチン間の信号伝播 (メタデータ伝播、信号伝播のキャンセル、タイムアウト制御などを含む)。

Golang では、go キーワードを使用して goroutine を開くことができるため、Go で同時実行コードを簡単に作成できます。しかし、これらの同時実行グルーチンを効果的に制御するにはどうすればよいでしょうか?

同時実行制御というと、多くの人は最初にロックを思い浮かべるかもしれません。 Golang は、ミューテックス ロック sync.Mutex や読み取り/書き込みロック sync.RWMutex などのロック関連のメカニズムも提供します。ロックに加えて、同期/アトミックなどのアトミック操作もあります。ただし、これらのメカニズムの焦点は、ゴルーチンの同時データの安全性にあります。この記事で議論したいのは、ゴルーチンの同時実行動作制御です。

ゴルーチンの同時動作制御には、WaitGroup、channel、Context という 3 つの一般的なメソッドがあります。

以上がGo言語で同時実行制御を実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。