基本的に、いつ正確に呼び出されることになっているのか、それに付随する API 呼び出しが必要なのかについて、大規模な混乱/あいまいさがあるようです。残念ながらPyEval_InitThreads()
、公式の Python ドキュメントは非常にあいまいです。このトピックに関するstackoverflowにはすでに多くの質問があり、実際、私はすでに個人的にこれとほぼ同じ質問をしているので、これが重複として閉じられても特に驚かないでしょう。しかし、この質問に対する決定的な答えはないように思われることを考慮してください。(残念ながら、スピード ダイヤルに Guido Van Rossum はいません。)
まず、ここで質問の範囲を定義しましょう。私は何をしたいですか? ええと...私はCでPython拡張モジュールを書きたいと思っています:
pthread
C の APIを使用してワーカー スレッドを生成する- これらの C スレッド内から Python コールバックを呼び出す
では、Python ドキュメント自体から始めましょう。Python 3.2のドキュメントには次のように書かれています。
void PyEval_InitThreads()
グローバル インタープリター ロックを初期化して取得します。2 番目のスレッドを作成する前、または PyEval_ReleaseThread(tstate) などの他のスレッド操作を実行する前に、メイン スレッドで呼び出す必要があります。PyEval_SaveThread() または PyEval_RestoreThread() を呼び出す前には必要ありません。
したがって、ここでの私の理解は次のとおりです。
- スレッドを生成する C 拡張モジュールは
PyEval_InitThreads()
、他のスレッドが生成される前にメイン スレッドから呼び出す必要があります。 - 呼び出し
PyEval_InitThreads
は GIL をロックします
PyEval_InitThreads()
したがって、スレッドを作成する C 拡張モジュールは、 を呼び出してから、グローバル インタープリター ロックを解放する必要があることが常識的に言えます。さて、十分に簡単に思えます。したがって、一応、必要なのは次のコードだけです。
PyEval_InitThreads(); /* initialize threading and acquire GIL */
PyEval_ReleaseLock(); /* Release GIL */
簡単に思えますが、残念ながら、Python 3.2 のドキュメントでは、それは非推奨PyEval_ReleaseLock
になったと書かれています。代わりにPyEval_SaveThread
、GIL をリリースするために使用することになっています。
PyThreadState* PyEval_SaveThread()
グローバル インタープリター ロックを解放し (ロックが作成され、スレッド サポートが有効になっている場合)、スレッド状態を NULL にリセットし、以前のスレッド状態 (NULL ではない) を返します。ロックが作成されている場合は、現在のスレッドがそれを取得している必要があります。
ええと...わかりました。C拡張モジュールは次のように言う必要があると思います:
PyEval_InitThreads();
PyThreadState* st = PyEval_SaveThread();
実際、これはまさにこのスタックオーバーフローの回答が言っていることです。実際にこれを実際に試してみた場合を除き、拡張モジュールをインポートすると、Python インタープリターはすぐにセグメンテーション違反を起こします。良い。
さて、私は今、公式の Python ドキュメントをあきらめて、Google に目を向けています。したがって、このランダムなブログでは、拡張モジュールから行う必要があるのは を呼び出すことだけだと主張していますPyEval_InitThreads()
。もちろん、ドキュメンテーションはPyEval_InitThreads()
が GIL を取得すると主張しており、実際にinのソース コードを簡単に調べると、PyEval_InitThreads()
ceval.c
実際に内部関数を呼び出していることがわかります。take_gil(PyThreadState_GET());
だからPyEval_InitThreads()
間違いなくGILを取得します。を呼び出した後、何らかの形でGILを解放する必要があると思いますPyEval_InitThreads()
。 しかし、どのように? PyEval_ReleaseLock()
非推奨であり、PyEval_SaveThread()
不可解なセグフォルトです。
わかりました...おそらく、現在私の理解を超えている何らかの理由で、C拡張モジュールはGILをリリースする必要はありません。私はそれを試しました...そして、予想通り、別のスレッドが(PyGILState_Ensureを使用して)GILを取得しようとするとすぐに、プログラムはデッドロックからハングします。そうそう... を呼び出した後、本当にGIL を解放する必要がありますPyEval_InitThreads()
。
繰り返しますが、問題は、呼び出し後に GIL をどのように解放するPyEval_InitThreads()
かです。
もっと一般的に言えば、ワーカーの C スレッドから Python コードを安全に呼び出せるようにするために、C 拡張モジュールは正確に何をしなければならないのでしょうか?