28

基本的に、いつ正確に呼び出されることになっているのか、それに付随する API 呼び出しが必要なのかについて、大規模な混乱/あいまいさがあるようです。残念ながらPyEval_InitThreads()公式の Python ドキュメントは非常にあいまいです。このトピックに関するstackoverflowにはすでに多くの質問があり、実際、私はすでに個人的にこれとほぼ同じ質問をしているので、これが重複として閉じられても特に驚かないでしょう。しかし、この質問に対する決定的な答えはないように思われることを考慮してください。(残念ながら、スピード ダイヤルに Guido Van Rossum はいません。)

まず、ここで質問の範囲を定義しましょう。私は何をしたいですか? ええと...私はCでPython拡張モジュールを書きたいと思っています:

  1. pthreadC の APIを使用してワーカー スレッドを生成する
  2. これらの C スレッド内から Python コールバックを呼び出す

では、Python ドキュメント自体から始めましょう。Python 3.2のドキュメントには次のように書かれています。

void PyEval_InitThreads()

グローバル インタープリター ロックを初期化して取得します。2 番目のスレッドを作成する前、または PyEval_ReleaseThread(tstate) などの他のスレッド操作を実行する前に、メイン スレッドで呼び出す必要があります。PyEval_SaveThread() または PyEval_RestoreThread() を呼び出す前には必要ありません。

したがって、ここでの私の理解は次のとおりです。

  1. スレッドを生成する C 拡張モジュールは PyEval_InitThreads()、他のスレッドが生成される前にメイン スレッドから呼び出す必要があります。
  2. 呼び出し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 拡張モジュールは正確に何をしなければならないのでしょうか?

4

7 に答える 7

15

あなたの理解は正しいです: 呼び出しPyEval_InitThreadsは、とりわけ、GIL を取得します。正しく記述された Python/C アプリケーションでは、GIL は自動または手動で時間内にロック解除されるため、これは問題ではありません。

メイン スレッドが引き続き Python コードを実行する場合、特別なことは何もありません。Python インタープリターは、多数の命令が実行された後に GIL を自動的に放棄するためです (別のスレッドが GIL を取得できるようにすると、GIL は再び放棄されます。の上)。さらに、Python がネットワークからの読み取りやファイルへの書き込みなど、ブロッキング システム コールを呼び出そうとしているときはいつでも、コールの周囲で GIL を解放します。

この回答の元のバージョンは、ほぼここで終了しました。しかし、考慮すべきことがもう 1 つあります。埋め込みのシナリオです。

Python を埋め込む場合、メイン スレッドは多くの場合、Python を初期化し、Python に関連しない他のタスクを実行します。そのシナリオでは、GIL を自動的に解放するものは何もないため、これはスレッド自体で行う必要があります。これは、 を呼び出す呼び出しに固有のものではなく、GIL を取得して呼び出されるすべての Python/C コードPyEval_InitThreadsで想定されます。

たとえば、次のmain()ようなコードが含まれている可能性があります。

Py_Initialize();
PyEval_InitThreads();

Py_BEGIN_ALLOW_THREADS
... call the non-Python part of the application here ...
Py_END_ALLOW_THREADS

Py_Finalize();

コードでスレッドを手動で作成する場合、 Python 関連の処理を実行する前に GIL を取得する必要がありPy_INCREFます。これを行うには、次を使用します。

// Acquire the GIL
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

... call Python code here ...

// Release the GIL. No Python API allowed beyond this point.
PyGILState_Release(gstate);
于 2013-03-18T07:16:42.823 に答える
6

私はあなたのような症状を見てきました: PyEval_InitThreads() だけを呼び出すとデッドロックが発生します。これは、メイン スレッドが Python から何も呼び出さないためです。症状は、Python のバージョンと状況によって異なります。私は、Python 拡張機能の一部としてロードできるライブラリ用に Python を埋め込むプラグインを開発しています。したがって、コードは、Python によってメインとしてロードされるかどうかに関係なく実行する必要があります。

以下は、python2.7 と python3.4 の両方で動作し、私のライブラリは Python 内と Python の外部で実行されています。メイン スレッドで実行されるプラグインの初期化ルーチンで、次を実行します。

  Py_InitializeEx(0);
  if (!PyEval_ThreadsInitialized()) {
    PyEval_InitThreads();
    PyThreadState* mainPyThread = PyEval_SaveThread();
  }

(mainPyThread は実際には静的変数ですが、二度と使用する必要がないため、重要ではないと思います)。

次に、pthreads を使用してスレッドを作成し、Python API にアクセスする必要がある各関数で、次を使用します。

  PyGILState_STATE gstate;
  gstate = PyGILState_Ensure();
  // Python C API calls
  PyGILState_Release(gstate);
于 2015-06-17T10:20:50.423 に答える
2

上記を引用するには:

簡単な答え: PyEval_InitThreads を呼び出した後に GIL を解放する必要はありません...

さて、より長い答えについては:

私は答えを Python の拡張機能に関するものに限定しています (Python の埋め込みではなく)。Python のみを拡張する場合、モジュールへのエントリ ポイントは Python からのものです。これは定義上、Python 以外のコンテキストから関数を呼び出すことを心配する必要がないことを意味します。これにより、処理が少し簡単になります。

スレッドが初期化されていない場合、GIL がないことがわかります (スレッドがない == ロックの必要がない) ため、「どのスレッド (存在する場合) が現在グローバルな通訳者ロック」は適用されません。

if (!PyEval_ThreadsInitialized())
{
    PyEval_InitThreads();
}

PyEval_InitThreads() を呼び出した後、GIL が作成され、現在 Python コードを実行しているスレッドであるスレッドに割り当てられます。だからすべてが良いです。

ここで、私たち自身が起動したワーカー "C" スレッドに関しては、関連するコードを実行する前に GIL を要求する必要があります。そのため、一般的な方法論は次のとおりです。

// Do only non-Python things up to this point
PyGILState_STATE state = PyGILState_Ensure();
// Do Python-things here, like PyRun_SimpleString(...)
PyGILState_Release(state);
// ... and now back to doing only non-Python things

拡張機能の通常の使用よりも、デッドロックについて心配する必要はありません。関数に入ったとき、Python を制御していたので、スレッドを使用していなかったか (したがって GIL を使用していなかった)、GIL が既に割り当てられていました。関数を終了して Python ランタイムに制御を戻すと、通常の処理ループが GIL をチェックし、必要に応じて他の要求オブジェクト (PyGILState_Ensure() によるワーカー スレッドを含む) に制御を渡します。

このすべては、おそらく読者はすでに知っているでしょう。しかし、「証拠はプリンにある」。私は、実際の動作がどのようなもので、適切に動作するかを自分で学ぶために、今日書いた非常に最小限の文書化された例を投稿しました。 GitHub のサンプル ソース コード

CMake と Python 開発の統合、SWIG と上記の両方の統合、Python の動作と拡張機能とスレッドなど、いくつかのことをこの例から学びました。それでも、この例の核心により、次のことが可能になります。

  • モジュールをロードします -- 'import annoy'
  • Python を実行する 0 個以上のワーカー スレッドをロードします -- 'annoy.annoy(n)'
  • すべてのワーカー スレッドをクリアします -- 'annon.annoy(0)'
  • アプリケーションの終了時に (Linux で) スレッドのクリーンアップを提供する

...そして、これらすべてがクラッシュやセグメンテーション違反なしで行われます。少なくとも私のシステム(Ubuntu Linux with GCC)では。

于 2015-09-16T19:45:44.893 に答える
1

私もこの問題について混乱を感じています。次のコードは偶然にも機能します。

Py_InitializeEx(0);
    if (!PyEval_ThreadsInitialized()) {
    PyEval_InitThreads();
    PyThreadState* mainPyThread = PyEval_SaveThread();
}

私のメイン スレッドは、いくつかの Python ランタイムの初期作業を行い、タスクを処理するために他の pthread を作成します。そして、これに対するより良い回避策があります。メインスレッドで:

if (!PyEval_ThreadsInitialized()){
    PyEval_InitThreads();
}
//other codes
while(alive) {
    Py_BEGIN_ALLOW_THREADS
    sleep or other block code
    Py_END_ALLOW_THREADS
}
于 2016-11-12T05:39:26.503 に答える
0

拡張モジュールでそれを呼び出す必要はありません。これは、C-API 拡張モジュールがインポートされている場合に既に行われているインタープリターを初期化するためのものです。このインターフェースは組み込みアプリケーションで使用されます。

PyEval_InitThreads はいつ呼び出されることになっていますか?

于 2013-03-18T06:40:22.090 に答える