次のコラムでは、golang チュートリアル コラムで Golang コルーチンのスケジューリングについて紹介します。困っている友人のお役に立てれば幸いです。
golang のコルーチン スケジューリングを見てみましょう
Groutine では、GPM スケジューリング モデルを通じて強力な同時実行性を実装できます。次に、Groutine スケジューリング モデルについて説明します。
Go のスケジューラには、内部に 3 つの重要な構造があります: M、P、G
M: M はカーネルレベルのスレッドのカプセル化であり、数値は実数に対応します。 CPU の M はスレッドであり、ゴルーチンは M 上で実行されます。M は、スモール オブジェクト メモリ キャッシュ (mcache)、現在実行中のゴルーチン、乱数ジェネレーターなどの多くの情報を保持する大きな構造です。
G : スケジューリングのための独自のスタック、命令ポインタ、その他の情報 (待機チャネルなど) を持つゴルーチンを表します。
P: P の正式名は Processor で、主な目的は goroutine を実行することです。各プロセッサ オブジェクトには LRQ (ローカル実行キュー) があります。未割り当ての Goroutine オブジェクトは GRQ (グローバル実行キュー) に保存され、LRQ 内の特定の P に割り当てられるのを待ちます。各 LRQ にはユーザーが作成した複数の Goroutine オブジェクトが含まれます。
Golang はマルチスレッド モデルを使用します。詳しく説明すると、これは 2 レベルのスレッド モデルですが、システム スレッド (カーネル レベルのスレッド) をカプセル化し、軽量のコルーチン goroutine を公開します。(ユーザー レベルのスレッド) ) はユーザーが使用できるもので、ユーザーレベルのスレッドからカーネルレベルのスレッドへのスケジューリングは golang のランタイムによって処理され、スケジューリング ロジックは外部に対して透過的です。 goroutine の利点は、コンテキストの切り替えが完全なユーザー状態で実行されることであり、スレッドほど頻繁にユーザー状態とカーネル状態を切り替える必要がないため、リソースの消費が節約されます。
上の図からわかるように、2 つの物理スレッド M があり、各 M にはプロセッサ P があり、それぞれに goroutine があります。ランニング。
P の数は GOMAXPROCS() を通じて設定できます。これは実際の同時実行性、つまり同時に実行できるゴルーチンの数を表します。
画像内の灰色のゴルーチンは実行されていませんが、準備完了状態にあり、スケジュールされるのを待っています。 P はこのキュー (runqueue と呼ばれます) を維持します。
Go 言語では、go 関数を実行するだけで goroutine を開始するのが簡単です。そのため、go ステートメントが実行されるたびに、
goroutine が runqueue の最後に追加されます。 queue. 、次のスケジューリング ポイントで、runqueue から goroutine が取り出され (どの goroutine を取得するかを決定する方法は?)、実行されます。
OS スレッド M0 がブロックされている場合 (以下に示すように)、P は代わりに M1 を実行しています。図内の M1 は、スレッド キャッシュから作成されているか、スレッド キャッシュから取り出されている可能性があります。
MO が戻ったら、ゴルーチンを実行するために P を取得しようとする必要があります。通常は、他の OS スレッドから P を取得します。
それを取得できない場合は、ゴルーチンをグローバルに置きます
runqueue を実行し、その後単独でスリープします (スレッド キャッシュに入れられます)。すべてのPも定期的にグローバルをチェックします
runqueue を作成し、その中で goroutine を実行します。そうしないと、グローバル runqueue 上の goroutine は実行されません。
もう 1 つの状況は、P によって割り当てられたタスク G がすぐに完了する (不均一な分散) ため、プロセッサ P は非常にビジー状態になりますが、他の P は依然としてビジー状態です。この時点でグローバルであれば、 実行キューにはタスク G がないため、P は他の P から G を取得して実行する必要があります。一般に、P が他の P からタスクを取得したい場合、通常は run がかかります キューの半分。これにより、以下に示すように、各 OS スレッドが完全に使用できるようになります。
1. P:
2. M の数:
M は P の数と絶対的な関係はありません。1 つの M がブロックすると、P は別の M を作成するか、別の M に切り替えます。したがって、P のデフォルトの数が 1 であっても、多くの M がブロックされる可能性があります。作成されました。
3. P の作成時: P の最大数 n を決定した後、ランタイム システムはこの数に基づいて n 個の P を作成します。
4. M が作成されるとき: P に関連付けてその中で実行可能な G を実行するのに十分な M がありません。たとえば、この時点ですべての M がブロックされており、P にまだ多くの準備完了タスクがある場合、アイドル状態の M が検索されます。アイドル状態の M がない場合は、新しい M が作成されます。
システムコールによりMがブロックされると(M上で動作するGがシステムコールに入る)、MとPが分離されますが、このときPのレディキューにタスクがあれば、
Pそれアイドル状態の M に関連付けられるか、関連付け用の M が作成されます。 (つまり、go は libtask のような IO ブロックを処理しませんか? よくわかりません。)
P のレディ キュー内のすべてのタスクが実行された場合、P は他の P のレディ キューからタスクを取り出そうとします。その一部は、各 P のレディ キューに実行するタスクがあることを保証するために、独自のレディ キューに入れられます。
以上がGolang コルーチンのスケジューリングについての詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。