TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

C/C++からPythonをマルチスレッドで使う

C/C++からPythonの処理をマルチスレッドで使うには、C/C++側でもGILの制御が必要になる。

マルチスレッドでGILを取得せずにPythonの処理を呼び出すとメモリ例外などで異常終了する。

Python仮想マシンはスレッドセーフではなくマルチスレッドでは動かせない。
マルチスレッドで動かすには、どれか一つのスレッドしか動かないようにGILと呼ばれるロックを取得する。
GILを取得しているスレッドがGILを解放すると他のスレッドが動作する。

Pythonの内部処理では、時間のかかるIO処理を行う際は、GILを解放するため、その間は他のスレッドが動作できる。
サードパーティーのライブラリでも、IO処理の際にはGILを解放するように実装されている。

C/C++からPythonの処理を呼び出す際は、GILを取得してから呼び出すようにする。
なお、シングルスレッドの場合は、GILを取得しなくても問題は起きない。

マルチスレッドで処理する際は、メインスレッドでGILを初期化しておく。

PyEval_InitThreads();

子スレッドでGILを取得するには、

PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

とする。

子スレッドでPythonの呼び出しを行った後、GILを解放するには、

PyGILState_Release(gstate);

とする。

Python処理から取得したオブジェクトをC/C++側で使用する場合、Pythonのオブジェクトを使用している間は、GILを解放してはいけない。

C/C++プログラムのメインスレッドで関数の取得などを行い、
子スレッドでPythonの処理を呼び出す場合は、メインスレッドでGILを解放しておく必要がある。

取得しているGILを解放するには、

PyThreadState *_save;
_save = PyEval_SaveThread();

とする。

子スレッドの処理が終了した後、

PyEval_RestoreThread(_save);

で、GILを取得する。

参考:
初期化 (initialization)、終了処理 (finalization)、スレッド — Python 3.10.0b2 ドキュメント