PythonのGlobal Interpreter Lock (GIL) について
Python (CPython) では、インタープリターはひとつしかない。このため、複数のスレッドを走らせた場合、ある時点ではひとつのスレッドしか動かない。このためのスレッド同期機構を、Global Interpreter Lock (GIL) という。PythonでのGIL自体は、1992年の初期実装から変更が無い。しかし、Lockのスレッド間引渡し方法が、Python 3.2から変更されている。これにより、CPUインテンシブプロセスでの複数コアで複数スレッドを動かした場合の性能劣化を改善している。
ここでは、2.7と3.5を比較してどのように違うかを説明する。Python 2.7は、interpreter_lock (GIL) と _Py_Ticker (GIL監視間隔 大まかに命令数100が標準)からなる。なお、切り替え間隔は、sys.setcheckintervalで設定することが出来る。この方式では、スレッド間でロックを取り合って実行を継続する。別コアで動いているスレッド間の場合、ロックの開放情報をシグナルで別コアに送るのは時間がかかる。このため、CPUインテンシブプロセス間のコンテキストスイッチは、ほぼ永遠に発生しない。このため、全体として処理効率が落ちる。
Python 3.5は、gil_locked (GIL) 、gil_drop_request (GIL要求)および gil_interval (GIL監視間隔 5000マイクロ秒 (5ミリ秒) が標準) からなる。なお、切り替え間隔は、sys.setswitchintervalで設定することが出来る。この場合、gil_drop_requestをスレッド間のフラグとして用いる。待ちスレッドがフラグを設定して要求し、一定時間後実行スレッドが、フラグを確認しスレッドのロックを開放し、待ちスレッドに実行件を譲る。これにより、マルチコアでも、効率的にコンテキストスイッチを行うことが出来る。
とはいえ、Python3.2以降に簡単に切り替えられない(たとえば、Python 2.xベースであり、Python 3.xへ移行できない)という場合もありうる。この場合、Pythonプロセスの実行するコアを指定する等の対策が必要と考えられる。言うまでも無く、CPUインテンシブプロセスが6つあれば、6つのコアにそれぞれ指定する等が考えられる。
マニュアル
- 3.5
- gil_locked/gil_interval (microsec)/INTERVAL等、関数としてはtake_gil等
- PyInterpreterState/PyThreadState等
- PyEval_EvalFrameEx =>ループとして、切り替えて動く
- コンテキストスイッチ部分
- Py_Main
- 2.7
その他の参考資料
- 3.2でのGIL更新(マルチコアでの性能劣化の話が記載されている)
- 3.2前後での性能比較等
- 古いGILでのトラブル例