Go 言語Java と比較した大きな利点の 1 つは、並行プログラムを簡単に作成できることです。 Go 言語には goroutine メカニズムが組み込まれており、goroutine を使用すると、同時実行プログラムを迅速に開発し、マルチコア プロセッサ リソースを有効に活用できます。この記事では、ゴルーチンのアプリケーションとそのスケジューリングの実装について学びます。
1. Go 言語の並行性サポート
Goroutine プログラミングを使用します (推奨: goビデオ チュートリアル)
go キーワードを使用して goroutine を作成します。呼び出す必要がある関数の前に go 宣言を置き、同じアドレス空間で関数を呼び出して実行すると、関数は独立した同時スレッドとして実行されます。この種のスレッドは Go 言語ではゴルーチンと呼ばれます。
ゴルーチンの使用法は次のとおりです。
//go 关键字放在方法调用前新建一个 goroutine 并执行方法体 go GetThingDone(param1, param2); //新建一个匿名方法并执行 go func(param1, param2) { }(val1, val2) //直接新建一个 goroutine 并在 goroutine 中执行代码块 go { //do someting... }
ゴルーチンはマルチコア CPU 環境で並列処理されるためです。コードのブロックが複数のゴルーチンで実行される場合、コードの並列処理が実現します。
プログラムの実行を知る必要がある場合、並列結果を取得するにはどうすればよいでしょうか?チャネルと組み合わせて使用する必要があります。
チャネルを使用して同時実行性を制御する
チャネルは、同時に実行される関数を同期し、それらに何らかの値渡しの通信メカニズムを提供するために使用されます。
チャネルを介して渡される要素のタイプ、コンテナ (またはバッファ)、および転送方向は、「
組み込み関数 make を使用してチャネルを割り当てることができます:
i := make(chan int) // by default the capacity is 0 s := make(chan string, 3) // non-zero capacity r := make(<-chan bool) // can only read from w := make(chan<- []os.FileInfo) // can only write to
ランタイムを構成します。GOMAXPROCS
次のコードを使用して明示的に設定しますマルチコアを使用するかどうか 同時タスクを実行するには:
runtime.GOMAXPROCS()
GOMAXPROCS の数はタスクの量に応じて割り当てることができますが、CPU コアの数を超えることはできません。
並列実行の構成は、CPU 集中型および高並列シナリオに適しています。IO 集中型の場合、複数のコアを使用すると、CPU の切り替えによるパフォーマンスの損失が増加します。
Go 言語の同時実行メカニズムを理解した後、ゴルーチン メカニズムの具体的な実装を見てみましょう。
#2. 並列処理と同時実行の違い
##プロセス、スレッド、プロセッサー
最新のオペレーティング システムでは、スレッドがプロセッサのスケジューリングと割り当ての基本単位であり、プロセスがリソース所有権の基本単位です。各プロセスは、プライベート仮想アドレス空間、コード、データ、その他のさまざまなシステム リソースで構成されます。スレッドはプロセス内の実行単位です。各プロセスには少なくとも 1 つのメイン実行スレッドがあり、ユーザーが積極的に作成する必要はなく、システムによって自動的に作成されます。ユーザーは必要に応じてアプリケーション内に他のスレッドを作成し、複数のスレッドが同じプロセス内で同時に実行されます。並列処理と同時実行
並列処理と同時実行 (同時実行と並列処理) は 2 つの異なる概念であり、これらを理解することは、マルチスレッド モデルを理解する上で非常に重要です。 プログラムの同時実行性または並列性を説明するときは、プロセスまたはスレッドの観点から記述する必要があります。 同時実行性: ある期間内に多くのスレッドまたはプロセスが実行されていますが、どの時点でも実行されているのは 1 つだけです。複数のスレッドまたはプロセスがタイム スライスを競合し、順番に実行します。Parallel : ある期間および時点で複数のスレッドまたはプロセスが実行されています。非並行プログラムには、垂直制御ロジックが 1 つだけあります。常に、プログラムはこの中の特定の位置にのみ存在します。制御ロジック、つまり順次実行。プログラムが特定の時点で複数の CPU パイプラインによって同時に処理される場合、そのプログラムは並列実行されていると言います。 並列処理にはハードウェアのサポートが必要です。シングルコア プロセッサは同時実行のみ可能で、マルチコア プロセッサは並列実行を実現できます。 同時実行性は並列処理の必要条件です。プログラム自体が同時実行でない場合、つまり論理的な実行順序が 1 つしかない場合、並列処理を許可することはできません。 同時実行性は並列処理の十分条件ではありません。同時プログラムが 1 つの CPU だけで (タイム シェアリングを通じて) 処理される場合、それは並列ではありません。 たとえば、非同時実行である「Hello World」を出力する最も単純な逐次構造プログラムを作成します。プログラムに複数のスレッドを追加し、各スレッドが「Hello World」を出力する場合、このプログラムは次のようになります。同時。実行時にこのプログラムに 1 つの CPU のみが割り当てられている場合、この同時プログラムはまだ並列化されていないため、プログラムの並列化を実現するにはマルチコア プロセッサ上にデプロイする必要があります。#3. いくつかの異なるマルチスレッド モデル
ユーザー スレッドとカーネル レベルのスレッドスレッドの実装は、ユーザーレベルのスレッド (User-LevelThread、ULT) とカーネルレベルのスレッド (Kemel-LevelThread、KLT) の 2 つのカテゴリに分類できます。ユーザー スレッドはユーザー コードによってサポートされ、カーネル スレッドはオペレーティング システム カーネルによってサポートされます。
マルチスレッド モデルマルチスレッド モデルは、ユーザー レベルのスレッドとカーネル レベルのスレッドの異なる接続方法です。
(1) 多対 1 モデル (M: 1)
複数のユーザーレベルのスレッドを 1 つのカーネルレベルのスレッドにマッピングすると、スレッド管理がユーザー空間で完了します。このモードでは、ユーザーレベルのスレッドはオペレーティング システムからは見えません (つまり、透過的です)。
利点: このモデルの利点は、スレッド コンテキストの切り替えがユーザー空間で発生し、パフォーマンスにプラスの影響を与えるモード切り替え (モード切り替え) を回避できることです。
欠点: すべてのスレッドは 1 つのカーネル スケジューリング エンティティ、つまりカーネル スレッドに基づいており、使用できるプロセッサは 1 つだけということになります。これはマルチプロセッサ環境では受け入れられません。本質的に、ユーザー スレッドのみが使用されます。同時実行の問題は解決しますが、並列問題は解決しません。 I/O 操作によりスレッドがカーネル状態に陥り、カーネル状態のスレッドが I/O データの待機をブロックした場合、すべてのスレッドがブロックされます。ユーザー空間ではノンブロッキング I/O も使用できますが、パフォーマンスと複雑さが低下します。避けられない程度の問題です。
(2) 1 対 1 モデル (1:1)
各ユーザーレベルのスレッドをカーネルレベルのスレッドにマッピングします。
各スレッドはカーネル スケジューラによって独立してスケジュールされるため、1 つのスレッドがブロックされても、他のスレッドには影響しません。
利点: マルチコア プロセッサ ハードウェアのサポートにより、カーネル空間スレッド モデルは真の並列処理をサポートします。1 つのスレッドがブロックされても、別のスレッドの実行が許可されるため、同時実行機能が強力になります。
欠点: ユーザーレベルのスレッドが作成されるたびに、それに対応するカーネルレベルのスレッドを作成する必要があるため、比較的高いオーバーヘッドが発生し、アプリケーションのパフォーマンスに影響します。
(3) 多対多モデル (M : N)
カーネル スレッドとユーザー スレッドの数の比率は M : N です。カーネル ユーザー空間は、次の利点を組み合わせています。最初の二つ。
このモデルでは、カーネル スレッド スケジューラとユーザー空間スレッド スケジューラが相互運用する必要があります。基本的に、複数のスレッドが複数のカーネル スレッドにバインドされているため、ほとんどのスレッド コンテキストの切り替えが発生します。ユーザー空間、および複数のカーネル スレッドがプロセッサ リソースを完全に活用できます。
4. ゴルーチン機構のスケジューリング実装
ゴルーチン機構は M:N スレッド モデルを実装しており、ゴルーチン機構はコルーチンです。 golang の組み込みスケジューラの実装。これにより、マルチコア CPU の各 CPU がコルーチンを実行できるようになります。
ゴルーチン メカニズムの原理を理解する鍵は、Go 言語スケジューラの実装を理解することです。
スケジューラの仕組み
Go 言語には、スケジューラの実装全体をサポートする 4 つの重要な構造 (M、G、P、および Sched) があります。 3 つの定義は runtime.h にあり、Sched は proc.c に定義されています。
Sched 構造はスケジューラであり、M と G およびスケジューラのステータス情報を格納するキューを維持します。
M 構造は Machine (オペレーティング システムによって管理されるシステム スレッド) です。Goroutine は M 上で実行されます。M は小さなオブジェクト メモリ キャッシュ (mcache) を維持する大きな構造で、現在の実行にはたくさんあります。ゴルーチン、乱数ジェネレーターなどに関する情報。
P 構造体は Processor、プロセッサーで、主な目的はゴルーチンを実行することであり、ゴルーチンのキュー (runqueue) を維持します。プロセッサは、N:1 スケジューリングから M:N スケジューリングへの移行を可能にする重要な部分です。
G は goroutine 実装のコア構造であり、スタック、命令ポインタ、ブロックされたチャネルなど goroutine のスケジュールに重要なその他の情報が含まれています。
プロセッサの数は、起動時に環境変数 GOMAXPROCS の値に設定されるか、実行時に関数 GOMAXPROCS() を呼び出すことによって設定されます。プロセッサの数が固定されているということは、常に GOMAXPROCS スレッドのみが go コードを実行していることを意味します。
マシン プロセッサとゴルーチンをそれぞれ三角形、長方形、円を使用して表します。
シングルコア プロセッサのシナリオでは、すべてのゴルーチンが同じ M システム スレッドで実行されます。各 M システム スレッドはプロセッサを維持します。いつでも、そこにあるプロセッサはゴルーチンは 1 つだけであり、他のゴルーチンは runqueue で待機しています。ゴルーチンは、独自のタイム スライスの実行を終了すると、コンテキストを放棄し、実行キューに戻ります。マルチコア プロセッサのシナリオでは、ゴルーチンを実行するために、各 M システム スレッドがプロセッサを保持します。
通常の状況では、スケジューラは上記のプロセスに従ってスケジュールを設定しますが、スレッドはブロックされます。スレッドのブロックに対する goroutine の処理を見てください。
スレッド ブロック
システム コールの実行など、実行中の goroutine がブロックされると、別のシステム スレッド (M1) が作成され、現在の M スレッドはそのプロセッサ P は新しいスレッドに転送されて実行されます。
runqueue の実行が完了しました
プロセッサの runqueue の 1 つが空の場合、Goroutine をスケジュールすることはできません。別のコンテキストからゴルーチンの半分を盗みます。
5. 同時実行の実装に関するさらなる考察
Go 言語の同時実行メカニズムにはまだ多くの点があります。 Go言語とScalaの同時実行実装の違い、Golang CSPとActorモデルの比較などを議論しました。
これらの同時実行メカニズムの実装を理解することは、同時実行プログラムの開発を改善し、パフォーマンスを最適化するのに役立ちます。
3 つのマルチスレッド モデルに関しては、Java 言語の実装に注目してください。
Java は、基礎となるオペレーティング システムの違いを JVM を通じてカプセル化しており、オペレーティング システムが異なれば、異なるスレッド モデルを使用する可能性があることはわかっています。たとえば、Linux と Windows では 1 対 1 モデルが使用される場合があります。 Solaris および UNIX のバージョンでは、多対多モデルを使用する場合があります。 JVM 仕様では、マルチスレッド モデルの具体的な実装は規定されておらず、1:1 (カーネル スレッド)、N:1 (ユーザー ステート スレッド)、および M:N (混合) モデルのいずれでも受け入れられます。 Java 言語のマルチスレッド モデルに関しては、特定の JVM に実装する必要があります。たとえば、Oracle/Sun の HotSpot VM は、デフォルトで 1:1 スレッド モデルを使用します。
以上がGo 言語の同時実行メカニズムの詳細な図による説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。