はじめに。
Pythonで並列処理を学習し始めたものの、「プロセス」「スレッド」「GIL」の話が出てきて白目になったのでまとめました。
用語一覧
用語 | 意味 |
---|---|
CPU(プロセッサ) | PCの脳みそ |
CPUコア | CPUの中核部分 |
マルチプロセッサ | CPUが複数 |
マルチコアプロセッサ | CPUコアが複数 (1CPU内) |
プロセス | 実行されたプログラムの実行単位 |
スレッド | プロセスを更に細かくした実行単位 |
コンテキストスイッチ | CPUが実行しているプロセス/スレッドを切り替える事。 |
スレッドセーフ | 複数スレッドが同時並行で実行しても問題無い状態の事。 |
グローバルインタプリタロック(GIL) | スレッド間のリソース競合を防ぐための排他ロック。(後述) |
インタプリタ | LL言語を逐一コンパイルして実行する事 |
I/O処理 | Input/Output処理の事 HDD,SSDの読み込みや、print等の出力 |
ライトウェイトプロセス(LWP) | スレッドを複数並行し実行するためのkernel内の機構。 |
以下、これら用語を用いて説明。
プロセスとスレッドの話
プロセスとはプログラムの実行単位の事であり、プロセスを立ち上げると実行コード、データ、リソースなどのイメージがメモリ上に展開され、それを呼び出すようになっている。対してスレッドとは1プロセス中に1つもしくは複数のスレッドが含まれており、このスレッドが実際にCPUで実行される単位となる。
各プロセスは固有のメモリを持っており、スレッドはプロセス内のメモリを共有する。つまりメモリの観点から、CPU100%フル活用の場合を除き、コンテキストスイッチのコストはスレッドの方が小さく、マルチプロセスよりもマルチスレッドの方が断然パフォーマンスは高い。
しかし、マルチスレッドの難点として複数のスレッド間で連携をとることが困難である。例えば、1つのグローバル変数と2つのスレッドがあった場合、1つのグローバル変数を各スレッドが意図しないタイミングで書き換え、もう一方で不都合が起きてしまう。いわゆる、スレッドセーフでない場合にこの問題が生じる。また別の例えとして、DBのデッドロックが挙げられる。
Pythonでは、このような状態を避ける方法として、次に説明するグローバルインタプリタロック(GIL)が採用されている。
グローバルインタプリタロック(GIL)の話
グローバルインタプリタロック(GIL)とは、LL言語(スクリプト言語)のインタプリタのスレッドによって保持されるスレッドセーフでないコードを、他のスレッドと共有して競合を起こしてしまう事を防ぐための排他ロック。つまり、GILを取得している1スレッドだけが、Pythonのコードを実行したりPythonのオブジェクトを操作できる。(Rubyもそんな感じと聞いた。)
故に、インタプリタの1プロセス毎に必ず1つのGILが存在し、GILを持つ言語で書かれたアプリケーションは、完全な並列性を得るために別々のプロセスを持つ必要がある。
GILを採用した際のデメリットとして、マルチプロセッサで実行させた場合、実行できるスレッドが1つに制限されるため、あまり恩寵を受けることが出来ない。また、プロセス生成のオーバーヘッド、メモリリソース、コンテキストスイッチのコストの問題が存在する。
一方で採用した場合のメリットは、言語側の都合によるもので、スレッドセーフでないC言語のライブラリとの結合が容易であるためである。
threadingとmultiprocessingの話
ここで、具体的にPythonモジュールである「threading」と「multiprocessing」の話に移る。
モジュール名 | 説明 |
---|---|
threading | 複数のスレッドを扱うためのモジュール |
multiprocessing | 複数のプロセスを扱うためのモジュール |
今までの説明からわかるように、threadingを使って複数のスレッドに割り振って動かしたとしても、グローバルインタプリタロック(GIL)の制約を受けるため複数スレッドが実行されず、ほぼ意味がない。(ただしI/O処理を除く)
一方でmultiprocessingでは複数のプロセスを立ち上げ、各プロセスでGILを取得した1スレッドが独立しているため、グローバルインタプリタロック(GIL)の問題を回避することができる。よって多少なりコンテキストスイッチ/メモリのコストは掛かるが、通常の処理よりこちらの方が高速に処理することが出来る。
まとめ
Pythonに限らず、プロセス/スレッドの理解は、他言語や鯖でも密接にかかわる分野なのである程度理解できていないとヤバい。(僕のように)
また、様々な文献から得た知識を繋ぎ合わせた程度でまだまだ理解が浅い故、今後も適宜勉強する必要があると同時に、コンパイル言語も学習すると良いのかなと感じた。
参考文献
マルチスレッド処理を理解しよう(前編)
マルチスレッド処理を理解しよう(後編)
Pythonの並列処理・並行処理のための標準モジュールの比較
グローバルインタプリタロック(GIL)とは
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典