4

私はPythonで書かれたネットワークライブラリを使用するCプログラムを書いています。pythonlibをpythonCapiに埋め込みます。ライブラリはすべてのリクエストを非同期で送信し、リクエストが完了するとシグナルで通知します。

それは理論的には意味します。

実際には、2つのスレッド関連の問題があります。

  1. cからpythonlibへのすべての呼び出しはブロック状です(すぐに戻る必要があります)
  2. Python libは、登録されたコールバックを非同期で呼び出します(thread.start_new_thread(callback、args))。これは機能しません(何も起こりません)。Pythonコードをcallback(args)に変更すると、機能します。

私が間違っていることは何ですか?マルチスレッドを機能させるために私がしなければならないことはありますか?

4

1 に答える 1

6

同様のシナリオがあります。

初期ワークフロー

  1. アプリケーションは C++ 層から開始します
  2. C++ レイヤーは、メイン スレッドの Python レイヤーで関数を呼び出します
  3. メイン スレッドの Python レイヤー関数は、イベント スレッドを作成します
  4. Python 層でイベント スレッドを開始し、C++ 層に戻ります
  5. メイン ループは C++ 層で開始します
  6. イベント スレッドは、必要に応じて 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_EnsurePyGILState_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 をマルチスレッドで埋め込むことができます。

変更後のワークフロー

  1. アプリケーションは C++ 層から開始します
  2. PyEval_InitThreads();マルチスレッドを有効にする
  3. save = PyEval_SaveThread();メインスレッドでGILを解放する
  4. state = PyGILState_Ensure();メインスレッドでGILを取得する
  5. C++ レイヤーは、メイン スレッドの Python レイヤーで関数を呼び出します
  6. メイン スレッドの Python レイヤー関数は、イベント スレッドを作成します
  7. Python 層でイベント スレッドを開始し、C++ 層に戻ります
  8. PyGILState_Release(state);メインスレッドでGILを解放する
  9. メイン ループは C++ 層で開始します
  10. イベント スレッドは、必要に応じて C++ レイヤーのコールバック関数を呼び出します。
于 2015-06-10T03:49:11.070 に答える