同様のシナリオがあります。
初期ワークフロー
- アプリケーションは C++ 層から開始します
- C++ レイヤーは、メイン スレッドの Python レイヤーで関数を呼び出します
- メイン スレッドの Python レイヤー関数は、イベント スレッドを作成します
- Python 層でイベント スレッドを開始し、C++ 層に戻ります
- メイン ループは C++ 層で開始します
- イベント スレッドは、必要に応じて C++ レイヤーのコールバック関数を呼び出します。
最初から、イベント スレッドは予期しない動作をします。これは私が遭遇した状況からGILが原因だと推測するので、GILからこれを解決しようとしました。これが私の解決策です。
分析
まず、PyEval_InitThreadsの注記から、
メインスレッドのみが存在する場合、GIL 操作は必要ありません。... したがって、ロックは最初は作成されません。...
したがって、マルチスレッドが必要な場合はPyEval_InitThreads()
、メイン スレッドで呼び出す必要があります。そして、私はPyEval_InitThreads()
前に電話しPy_Initialize()
ます。これで GIL が初期化され、メイン スレッドが GIL を取得します。
次に、Python 関数が C++ 層から呼び出される前に、PyGILState_Ensure()
GIL を取得するために呼び出されます。さらに、Python 関数が呼び出された後、 が呼び出されて、PyGILState_Release(state)
以前の GIL 状態に戻ります。その結果、ステップ 2 の前にPyGILState_Ensure()
が呼び出され、ステップ 4 の後にPyGILState_Release(state)
が呼び出されます。
しかし問題がある。PyGILState_EnsureとPyGILState_Releaseから、これら 2 つの関数は現在の GIL 状態を保存して GIL を取得し、以前の GIL 状態を復元して GIL を解放します。PyEval_InitThreads()
ただし、メイン スレッドで呼び出した後は、メイン スレッドが確実に GIL を所有します。また、メイン スレッドでの GIL の状態は次のとおりです。
/* main thread owns GIL by PyEval_InitThreads */
state = PyGILState_Ensure();
/* main thread owns GIL by PyGILState_Ensure */
...
/* invoke Python function */
...
PyGILState_Release(state);
/* main thread owns GIL due to go back to previous state */
上記のコード サンプルから、メイン スレッドは常に GIL を所有するため、イベント スレッドは実行されません。この状況を克服するには、メイン スレッドが を呼び出す前に GIL を取得しないようにしPyGILState_Ensure()
ます。したがって、 を呼び出した後PyGILState_Release(state)
、メイン スレッドは GIL を解放して、イベント スレッドを実行させることができます。そのため、GIL が初期化されたらすぐに GIL をメイン スレッドで解放する必要があります。
こちらPyEval_SaveThread()
が使用されています。PyEval_SaveThreadから、
グローバル インタープリター ロックを解放し (ロックが作成され、スレッド サポートが有効になっている場合)、スレッド状態を NULL にリセットします。
そうすることで、Python をマルチスレッドで埋め込むことができます。
変更後のワークフロー
- アプリケーションは C++ 層から開始します
PyEval_InitThreads();
マルチスレッドを有効にする
save = PyEval_SaveThread();
メインスレッドでGILを解放する
state = PyGILState_Ensure();
メインスレッドでGILを取得する
- C++ レイヤーは、メイン スレッドの Python レイヤーで関数を呼び出します
- メイン スレッドの Python レイヤー関数は、イベント スレッドを作成します
- Python 層でイベント スレッドを開始し、C++ 層に戻ります
PyGILState_Release(state);
メインスレッドでGILを解放する
- メイン ループは C++ 層で開始します
- イベント スレッドは、必要に応じて C++ レイヤーのコールバック関数を呼び出します。