プロセスとスレッド
- プロセス:プロセスは、オペレーティングシステム内のプログラムの実行プロセスであり、リソースの割り当てとスケジューリングのためのシステムの独立したユニットです。
- スレッド:スレッドは、プロセスの実行エンティティであり、CPUのスケジューリングとディスパッチの基本単位であり、プロセスよりも小さく、独立して実行できる基本単位です。
- 2つの関係:プロセスは複数のスレッドを作成およびキャンセルでき、同じプロセス内の複数のスレッドは同時に実行できます。
Goのコルーチン
- コルーチン:独立したスタックスペース、共有ヒープスペース、スケジューリングは、ユーザーレベルのスレッドと同様に、ユーザーによって制御されます。
- スレッドとの関係:スレッドは複数のコルーチンを作成でき、コルーチンは軽量スレッドです。
GoのGoroutine
java / c ++で並行プログラミングを実装する場合、通常は自分でスレッドプールを維持する必要があり、タスクを次々にパッケージ化する必要があり、タスクを実行してコンテキスト切り替えを維持するためにスレッドをスケジュールする必要があります。多くのプログラマーの心を消費します。では、プログラマーが多くのタスクを定義するだけで、システムがこれらのタスクをCPUに割り当てて同時実行できるようにするメカニズムはありますか?
Go言語のゴルーチンはそのようなメカニズムです。ゴルーチンの概念はスレッドに似ていますが、ゴルーチンはGoランタイムによってスケジュールおよび管理されます。Goプログラムは、ゴルーチン内のタスクを各CPUにインテリジェントに割り当てます。Go言語は、言語レベルでスケジューリングとコンテキスト切り替えのメカニズムが組み込まれているため、最新のプログラミング言語と呼ばれています。
Go言語プログラミングでは、プロセス、スレッド、コルーチンを自分で作成する必要はありません。スキルパックには、ゴルーチンという1つのスキルしかありません。タスクを同時に実行する必要がある場合は、このタスクを1つにパッケージ化するだけで済みます。関数、この関数を実行するためにコルーチンを開始するだけです、それはとても単純で失礼です。
GoでのGoroutineの使用
呼び出される関数の前にgo
キーワードを追加することで、関数のゴルーチンを作成できます
- コード例:
import (
"fmt"
)
func hello(){
fmt.Println("Hello World!")
}
func main() {
go hello()
fmt.Println("hello main")
}
- 実行結果:
ここでは、main関数のコンテンツのみが出力され、関数のコンテンツは出力されませんhello()
。その理由は、ここmain
でのメイン関数として、それがGoroutine,main
戻ったときに終了するデフォルトの関数でもあり、goroutine
もちろんメイン関数で開始された残りは実行できないためです。を追加するtime.sleep
ことで効果を得ることができます。 - コード例:
import (
"fmt"
"time"
)
func hello(){
fmt.Println("Hello World!")
}
func main() {
go hello()
fmt.Println("hello main")
time.Sleep(time.Second)
}
- 実行効果:
ここでtime.sleep
、main
関数のgoroutine
実行を一時停止することによりmain
、復帰遅延hello()
を実行することができます。 - 複数のゴルーチンの使用複数のゴルーチンを使用する
と、Go言語の並行性効果を実現できます。
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func hello2(i int) {
defer wg.Done()//goroutine结束就登记-1
fmt.Println("Hello goroutine",i)
}
func main() {
for i:=0;i<10;i++ {
wg.Add(1)//启动一个goroutine就登记+1
go hello2(i)
}
wg.Wait()//等到所有的goroutine结束
}
ゴルーチンとスレッドの主な違い
线程
:CPUのスケジューリングとディスパッチの基本単位。プロセスには複数のスレッドが存在することがよくあります。スレッドは基本的にリソースを所有しませんが、プロセスのリソースを共有しますが、各スレッドには、プログラムカウンター、レジスタのセット、スタックなどの補助スレッド実行リソースがあります。スレッド間の通信は主に共有メモリに依存し、コンテキストスイッチングは高速で、リソースのオーバーヘッドは比較的小さいです。协程
:コルーチンはユーザーモードの軽量スレッドであり、コルーチンのスケジューリングはユーザーによって完全に制御されます。コルーチンには、独自のレジスタ、コンテキストスイッチ、およびスタックがあります。コルーチンの切り替えがスケジュールされると、レジスタコンテキストとスタックが他の場所に保存されます。元に戻すと、以前に保存されたレジスタコンテキストとスタックが復元されます。スタックを直接操作すると、基本的にコア切り替えのオーバーヘッドがなく、ロックせずにアクセスできます。グローバル変数なので、コンテキストの切り替えは非常に高速です。
区别
:
-
スレッドは複数のコルーチンを持つことができ、プロセスは複数のコルーチンを別々に持つこともできるため、マルチコアCPUを外出先で使用できます。
-
スレッドプロセスは同期メカニズムですが、コルーチンは非同期です
-
コルーチンは最後の呼び出しの状態を保持でき、プロセスが再入力されるたびに、最後の呼び出しの状態に入るのと同じです。
-
OSスレッド(オペレーティングシステムスレッド)には通常、固定スタックメモリ(通常は2MB)があり、ゴルーチンのスタックにはライフサイクルの開始時に小さなスタック(通常は2KB)しかなく、ゴルーチンのスタックは固定されていません。増やすことができます。このサイズはめったに使用されませんが、必要に応じて減少し、ゴルーチンのスタックサイズ制限は1GBに達する可能性があります。したがって、Go言語で一度に約100,000個のゴルーチンを作成することが可能です。
ゴルーチンスケジューリング
GPMは、Go言語の実行レベルの実装であり、Go言語によって実装されるコルーチンスケジューリングシステムのセットです。OSスレッドをスケジュールするオペレーティングシステムとは異なります。
- Gはゴルーチンオブジェクトを表し、goが呼び出されるたびにGが作成されます
- Pは一連のゴルーチンキューを管理します。Pは現在のゴルーチンのコンテキスト(関数ポインタ、スタックアドレス、アドレス境界)を格納します。Pは管理するゴルーチンキュー(たとえば、時間がかかるゴルーチン)でスケジューリングを行います。 CPU内)一時停止、後続のゴルーチンの実行など)自分のキューが消費されたら、グローバルキューに移動してフェッチします。グローバルキューも消費されている場合は、他のPキューに移動してタスクを取得します。
- Mは、オペレーティングシステムのカーネルスレッドに移動する仮想操作です。Mとカーネルスレッドは、通常、1対1のマッピング関係にあります。ゴルーチンは、最終的にMで実行されます。
- PとMは一般的に1対1に対応します。それらの関係は次のとおりです。PはGマウントのグループを管理し、Mで実行されます。GがMで長時間ブロックされると、ランタイムは新しいMを作成し、ブロックされたGが配置されているPは、新しく作成されたMに他のGをマウントします。古いGがブロックされているか、死んでいると見なされたときに、古いMを取り戻します。
- Pの数はruntime.GOMAXPROCS(最大256)によって設定され、Go1.5バージョン以降、デフォルトは物理スレッドの数です。並行性の量が多い場合、PとMがいくらか追加されますが、あまり多くはありません。切り替えが頻繁すぎる場合、ゲインは損失の価値がありません。
- スレッドスケジューリングのみの観点から、他の言語に対するGo言語の利点は、OSスレッドがOSカーネルによってスケジュールされ、ゴルーチンがGoランタイム自体のスケジューラーによってスケジュールされることです。このスケジューラーはm:nと呼ばれるスケジューラーを使用します。スケジューリング技術(m個のカーネルをn個のOSスレッドに再利用/スケジューリングする)。その主な機能の1つは、ゴルーチンのスケジューリングがユーザーモードで行われ、メモリの割り当てや解放など、カーネルモードとユーザーモードを頻繁に切り替える必要がないことです。ユーザーモードで大きなメモリプールを維持します。のmalloc関数を呼び出すシステムを直接(メモリプールを変更する必要がない限り)、OSスレッドをスケジュールするよりもはるかに安価です。一方、マルチコアハードウェアリソースは十分に活用されており、いくつかのゴルーチンは物理スレッド間でほぼ均等に分割されています。ゴルーチン自体の超軽量と相まって、とりわけ、Goスケジューリングのパフォーマンスが保証されます。