1. ホーム
  2. スクリプト・コラム
  3. ゴラン

ゴルーチンモデルとスケジューリング戦略へのGo並行処理アプローチ

2022-01-06 07:25:50

劉丹冰の「quot;8 hours to golang engineer"」を学ぶ、このセクションはすべて原理についてです。

シングルプロセス・オペレーティングシステム

初期のシングルプロセスOSは、タイムラインが1つしかなく、CPUが各プロセス/スレッドを順次実行する、CPUが一度に1つの命令をインテリジェントに処理し、1つのタスクを処理する逐次実行と理解することができる

これは、CPUが現在のプロセスで立ち往生して待ち続けるとプロセスブロッキングになり、CPUが無駄になってしまいます

マルチスレッド/マルチプロセスのオペレーティングシステム

CPUは、各プロセスをスケジュールするポーリング機構を使用して、各プロセスは、固定タイムスライスを割り当てられ、このタイムスライスは非常に小さく、最初の実行プロセスAは、Aは、次のプロセスBに切り替えるには問題ありませんが終了した場合、タイムスライスはAは、次のプロセスB、およびに切り替えることを強制されます気にしない、CPUは、プロセス内のブロックを避けてください

しかし、この問題は、頻繁にプロセスを切り替える過程で、プロセスの切り替えは、必然的に現在のスレッドの状態を保存するなど、システムコール、環境のコンテキストスイッチング、および様々なコピーコピーは、ほとんどの時間を無駄につながるスイッチングコスト、およびプロセスの数が多ければ多いほど、スイッチングの無駄が大きいということである

つまり、このソフトウェアの目的はCPU使用率の向上です

一方、プロセスのメモリフットプリントも大きな問題です

1:Nモデル

プログラマの仕事は、ユーザー状態でインターフェイスをチューニングし、ビジネスを開発することです。カーネル状態は、ハードウェアのチューニングとシステムリソースのチューニングを担当し、ユーザースレッドとカーネルスレッドは1つずつ結合され、CPUはカーネルスレッドを管理するだけです。このようなカーネルスレッドをスレッドと呼び、ユーザースレッドを共同ルーチン、スレッドは同時スケジューラを管理することにより複数の同時スレッドを管理し、CPUはまだ1スレッドを管理するだけようにします。

これにより、ユーザー状態での並行処理が可能となり、CPUは切り替える必要がなく、並行処理間の切り替え時にわずかなリソースを消費するだけで、CPU消費量の多いスケジューリングという問題を解決することができます

欠点は、同時進行の1つのプロセスがブロックすると、次のプロセスが実行できないことです

M:Nモデル

M個のスレッドは並列スケジューラで管理されます。これは、CPUスケジューリングを最適化できないため、非常に重要です

ゴルーチン

goでは、同時実行のコルーチンがゴルーチンに変更され、1つのゴルーチンは数キロバイトしか占有しないので、多数のゴルーチンを存在させることができ、その一方でゴルーチンのスケジューラは非常に柔軟である

ゴルーチンアーリースケジューラ

初期のゴルーチン スケジューラには、ロックを持つグローバルなゴルーチン キューがあり、ゴルーチンを作成、破棄、または実行しようとするすべてのスレッドは、まずロックを取得し、次にゴルーチンを実行し、実行完了後にロックを返さなければなりませんでした。

この場合の問題は、激しいロック競争です

一方、あるスレッドがゴルーチンを実行すると、このゴルーチンは新しいゴルーチンを生成し、同時実行性を保証するために実行しなければならず、この新しく生成されたものは次に実行されるスレッドに入れられるので、実はプログラムの局所性原則を満たさないのです

さらに、異なるスレッドを頻繁に呼び出すと、やはりシステムオーバーヘッドが発生します

GMP

オペレーティングシステムでは、異なるカーネルスレッドを処理するためにCPUをスケジュールするオペレーティングシステムのスケジューラがあり、その上で各スレッドはプロセッサを持ち、このプロセッサはゴルーチン、ヒープ、スタック、データなどの様々なリソースを持っています。各Pは自身のローカルキューを管理し、このキューにはこのPがgoroutineを処理したいgoroutineが保持され、goroutineを処理する場合、スレッドはPを取得し、Pはスレッドにgoroutineをディスパッチし、スレッドはgoroutineを処理し、Pのローカルキューとは別にグローバルキューが存在し、ここにも実行すべきgoroutineを保持しています。

スケジューラ設計戦略

 スレッドの多重化

ワークスティーリング

スレッドがゴルーチンを実行中で、そのPがローカルキューに保留中のゴルーチンを持っている場合、他のPはそのスレッドのためにゴルーチンを盗みます(それが空いている場合)。

受け渡す

例えばPが実行しているゴルーチンが入力待ちなどで突然ブロックした場合、その時点ではCPUであるPのスレッドは実際には利用されておらず、その時点で新しいスレッドが生成/起動され、その時点でブロックしているゴルーチンはブロック継続が許され、現在のCPUはスリープ状態になります。が、物理的なCPUはノンブロッキングスレッドに切り替わり、PとPのローカルキューの残りは直接新しいスレッドに転送して制御し、前のブロックルーチンが再びブロックしていない場合は、再び別のキューにこのgoroutineを追加した後

パラレル

Pの数は、CPUコアの数÷2などマクロで定義可能

先取り

1:1 モデルでは、同時実行スレッドはスレッドにバインドされており、別の同時実行スレッドが実行する必要がある場合、現在の同時実行スレッドはそのスレッドのリソースを解放しない限り新しいスレッドを待つ必要があります

ゴルーチンの仕組みは、1つのゴルーチンが10msしかなく、時間がなくなると新しいゴルーチンが確実にCPUを奪い、誰も優先されず、全員が公平になります

グローバルキュー

ワークスティーリングに従って、実行するゴルーチンを持たないスレッドは、それを盗むために他のローカルキューに行き、他のキューに盗むためのものがなければ、それを得るためにグローバルキューに行き、グローバルキュー内の他のゴルーチンは前進する

Goのゴルーチンモデルとスケジューリング戦略の詳細は、スクリプトハウスの過去記事を検索するか、以下の関連記事を引き続き閲覧してください!今後ともスクリプトハウスをよろしくお願いします。