プロセスとスレッドとgoroutine
スレッドとプロセスについて
コンピュータプログラムの実行単位として、プロセスとスレッドがあります。
プロセスとはプログラムの実行インスタンスを指し、OSによって管理されます。プロセスは独自のメモリ空間を持ち、他のプロセスとメモリ空間を共有しません。そのため、プロセス間通信(IPC)を有効にしない限り、他のプロセスに影響を与えることはありません。しかし、プロセスの作成や切り替えには比較的大きなオーバーヘッドが伴います。
一方、スレッドはプロセス内で実行される軽量な実行単位です。プロセスは一つ以上のスレッドを持つことができ、スレッドはプロセスのメモリ空間を共有します。つまり、同じプロセス内のスレッドは共通のデータにアクセスすることができます。スレッド間の通信はプロセス間通信より効率的であり、スレッドは軽量であるため、作成や切り替えのオーバーヘッドが低いです。
項目 | プロセス | スレッド |
---|---|---|
定義 | 独立したプログラムの実行インスタンス | プロセス内の軽量な実行単位 |
メモリ空間 | 独立している | 共有している |
独立性 | 高い | 低い |
オーバーヘッド | 高い(作成・切り替えが重い) | 低い(作成・切り替えが軽い) |
通信方法 | プロセス間通信(IPC) | プロセス内通信(直接共有) |
CPUとの関係
CPUはオペレーティングシステムのスケジューラによってプロセスを管理します。スケジューラは、どのプロセスがいつ実行されるかを決定し、CPU時間を公平に分配します。これにより、複数のプロセスが効率的に実行されます。
CPUには複数のコアがあり、それぞれのコアが同時に1つのプロセスを実行できます。例えば、4つのコアを持つCPUは、同時に4つのプロセスを実行することができます。しかし、たとえコア数が1つしかない場合でも、CPUはタイムシェアリングによって複数のプロセスを交代で実行することができます。タイムシェアリングでは、CPUが高速でプロセス間を切り替えるため、ユーザーには複数のプロセスが同時に実行されているように見えます。
コンテキストスイッチ
- プロセス間の切り替えにはコンテキストスイッチが呼び出されます。コンテキストスイッチは現在のプロセスの状態(レジスタやプログラムカウンタなど)を保存し、次のプロセスの状態を復元する作業です。これはオーバーヘッドが大きいため、頻繁なコンテキストスイッチはシステムのパフォーマンスに影響を与える可能性があります。
スレッド
- CPUはマルチスレッド化されたアプリケーションを並列に処理できます。これにより、マルチコアプロセッサの能力を最大限に活用できます。スレッドは同じメモリ空間を共有するため、データの交換やリソースの共有が効率的です。CPUはスレッドのスケジューリングも行います。これはプロセス内のスレッドが公平にCPU時間を得られるようにするためです。スレッドのスケジューリングはプロセスのスケジューリングよりも高速です。
goroutineとスレッドの関係
goroutineとは
- goroutineは軽量なスレッドのようなもので、関数をgoroutineとして実行することで並行処理が行えます。Goのランタイムによって管理されます。
実行
- goroutineはOSスレッド上で実行されますが、Goランタイムがgoroutineのスケジューリングを行います。これにより、Goプログラムは少数のOSスレッドで多数のgoroutineを効率的に実行できます。
スケジューリング
- Goランタイムは、Mスケジューリングモデルを使用します。これは、M個のgoroutineをN個のOSスレッドで実行するモデルです。具体的には、たくさんのgoroutine(M)を少数のOSスレッド(N)で効率的に管理・実行する仕組みです。これにより、goroutineはOSスレッドに比べて非常に軽量で、スケジューリングのオーバーヘッドも少なくなります。
goroutineとスレッドの基本的な違い
-
goroutine:
- 非常に軽量なため、数万のgoroutineを作成しても問題ありません。
- 初期スタックサイズが小さいため、必要に応じて動的に拡張されます。
- Goランタイムによってスケジュールされるため、OSのスケジューラには管理されません。
-
スレッド:
- スレッドはOSによって管理されます。
- スレッドはgoroutineよりも重く、数万のスレッドを作成するとリソースが不足します。
Mスケジューリングモデルの詳細
-
ゴルーチン(goroutine):
- Go言語での並行実行単位。
- 非常に軽量で、Goランタイムがスケジューリングを担当。
-
OSスレッド:
- OSによって管理される実行単位。
- Goランタイムは、必要に応じてこれらのスレッドを作成し、管理。
スケジューリングの仕組み
- Goランタイムは、複数のgoroutineを少数のOSスレッドで効率的に実行します。goroutineがブロックされる(例:I/O待ちやチャネル操作で待機)場合、Goランタイムはそのgoroutineを別のOSスレッドに移動させ、ブロックされていない他のgoroutineを実行します。
具体的な動作
-
初期設定:
- プログラム開始時に、Goランタイムは少数のOSスレッド(N)を作成します。その上で、多数のgoroutine(M)が実行されます。
-
スケジューリング:
- 各goroutineはランタイムによって管理され、適切なOSスレッドに割り当てられます。OSスレッドがブロックされると、ランタイムは他のOSスレッドを利用して、他のgoroutineの実行を続けます。
-
スレッドの再利用:
- goroutineが終了したり、ブロック状態から解放されると、ランタイムはそのスレッドを再利用します。
-
1つのスレッド上で複数のgoroutineが実行される:
- goroutineは1つのスレッド上で順次実行されますが、スケジューラがgoroutine間の切り替えを行うことで、並行して実行されているように見えます。
-
goroutineは必ずしも1つのスレッド上で動くわけではない:
- goroutineは、状況に応じて異なるスレッドで実行されることがあります。例えば、あるgoroutineがI/O操作でブロックされた場合、そのgoroutineを別のスレッドに移動させて他のgoroutineを実行します。
スケジューリングのイメージ
- マッピング: GoランタイムはM個のgoroutineをN個のOSスレッドにマッピングします。例えば、1000個のgoroutineを10個のOSスレッドで実行する場合、各スレッドは平均して100個のgoroutineを処理します。
- 動的な調整: Goランタイムは動的にgoroutineをスケジューリングし、負荷やブロッキングに応じてスレッド間でgoroutineを移動させます。
これにより、効率的な並行実行が実現されます。
プロセス > スレッド > goroutine
まとめ
-
プロセス配下にスレッド:
- プロセスの中にスレッドが存在し、これらはOSによって管理されます。
-
スレッド上にgoroutine:
- goroutineはGoランタイムによって管理され、スレッド上で実行されます。Goランタイムがgoroutineをスケジュールし、適切なスレッドに割り当てます。
-
スケジューリングの役割分担:
- OSがプロセスとスレッドをスケジュールします。
- Goランタイムがgoroutineをスケジュールし、OSスレッドに割り当てます。
補足:
- Goランタイムが必要に応じて自動的にスレッドを作成できます。
- OSスレッドのスケジューリングはOSが行うという点は変わりません。
参考文献:
Discussion