Python インタプリタは完全にスレッド安全 (thread safe) ではありません。 マルチスレッドの Python プログラムをサポートするために、グローバルな ロックが存在していて、現在のスレッドが Python オブジェクトに安全に アクセスする前に必ずロックを獲得しなければならなくなっています。 ロック機構がなければ、単純な操作でさえ、マルチスレッドプログラムの 実行に問題を引き起こす可能性があります: たとえば、二つのスレッドが 同じオブジェクトの参照カウントを同時にインクリメントすると、 結果的に参照カウントは二回でなく一回だけしかインクリメントされない かもしれません。
このため、グローバルインタプリタロックを獲得したスレッドだけが Python オブジェクトを操作したり、 Python/C API 関数を呼び出したり できるというルールがあります。マルチスレッドの Python プログラムを サポートするため、インタプリタは定期的に -- デフォルトの設定では バイトコード 100 命令ごとに (この値は sys.setcheckinterval() で 変更できます) -- ロックを解放したり獲得したりします。 このロックはブロックが起こりうる I/O 操作の付近でも解放・獲得 され、I/O を要求するスレッドが I/O 操作の完了を待つ間、他の スレッドが動作できるようにしています。
Python インタプリタはスレッドごとに何らかの予約情報を持っておかねば なりません -- このため、Python は PyThreadState と呼ばれるデータ構造 を用います。 とはいえ、グローバル変数はまだ一つだけ残っています: それは現在の PyThreadState 構造体を指すポインタです。 ほとんどのスレッドパッケージが ``スレッドごとのグローバルデータ'' を保存する手段を持っている一方で、Python の内部的なプラットフォーム 非依存のスレッド抽象層はこれをサポートしていません。従って、 現在のスレッド状態を明示的に操作するようにしなければなりません。
ほとんどのケースで、このような操作は十分簡単にできます。 グローバルインタプリタロックを操作数ほとんどのコードは、以下のような 単純な構造を持ちます:
スレッド状態をローカル変数に保存する。 インタプリタロックを解放する。 ...ブロックが起きるような何らかの I/O 操作... インタプリタロックを獲得する。 ローカル変数からスレッド状態を回復する。
このやりかたは非常に一般的なので、作業を単純にするために二つの マクロが用意されています:
Py_BEGIN_ALLOW_THREADS ...ブロックが起きるような何らかの I/O 操作... Py_END_ALLOW_THREADS
Py_BEGIN_ALLOW_THREADS マクロは新たなブロック文を開始し、隠しローカル変数を宣言します; Py_END_ALLOW_THREADS はブロック文を終了します。これらの二つのマクロを使うもうひとつの 利点は、Python をスレッドサポートなしでコンパイルしたとき、 マクロの内容、すなわちスレッド状態の退避とロック操作が空になると いう点です。
スレッドサポートが有効になっている場合、上記のブロックは 以下のようなコードに展開されます:
PyThreadState *_save; _save = PyEval_SaveThread(); ...ブロックが起きるような何らかの I/O 操作... PyEval_RestoreThread(_save);
より低水準のプリミティブを使うと、以下のようにしてほぼ同じ効果を 得られます:
PyThreadState *_save; _save = PyThreadState_Swap(NULL); PyEval_ReleaseLock(); ...ブロックが起きるような何らかの I/O 操作... PyEval_AcquireLock(); PyThreadState_Swap(_save);
上の二つには微妙な違いがあります; とりわけ、 PyEval_RestoreThread() はグローバル変数 errno の値を保存しておいて 元に戻す点が異なります。というのは、ロック操作が errno に 何もしないという保証がないからです。また、スレッドサポートが無効化 されている場合、 PyEval_SaveThread() および PyEval_RestoreThread() はロックを操作しません; この場合、 PyEval_ReleaseLock() および PyEval_AcquireLock() は 利用できません。この仕様は、スレッドサポートを無効化してコンパイル されているインタプリタが、スレッドサポートが有効化された状態で コンパイルされている動的ロード拡張モジュールをロードできる ようにするためのものです。
グローバルインタプリタロックは、現在のスレッド状態を指すポインタを 保護するために使われます。ロックを解放してスレッド状態を退避する際、 ロックを解放する前に現在のスレッド状態ポインタを取得しておかなければ なりません (他のスレッドがすぐさまロックを獲得して、自らの スレッド状態をグローバル変数に保存してしまうかもしれないからです)。 逆に、ロックを獲得してスレッド状態を復帰する際には、 グローバル変数にスレッド状態ポインタを保存する前にロックを獲得して おかなければなりません。
なぜここまで詳しく説明しようとするかおわかりでしょうか? それは、 C でスレッドを生成した場合、そのスレッドにはグローバルインタプリタ ロックがなく、スレッド状態データ構造体もないからです。このような スレッドが Python/C API を利用するには、まずスレッド状態データ構造体を 生成し、次にロックを獲得し、そしてスレッド状態ポインタを保存すると いったように、自分自身をブートストラップして生成しなければ なりません。スレッドが作業を終えたら、スレッド状態ポインタを リセットして、ロックを解放し、最後にスレッド状態データ構造体を メモリ解放しなければなりません。
スレッドデータ構造体を生成する際には、インタプリタ状態データ構造体を 指定する必要があります。インタプリタ状態データ構造体は、 インタプリタ内の全てのスレッド間で共有されているグローバルなデータ、 例えばモジュール管理データ (codesys.modules) を保持しています。 必要に応じて、新たなインタプリタ状態データ構造体を作成するなり、 Python メインスレッドが使っているインタプリタ状態データ構造体 を共有するなりできます (後者のデータにアクセスするためには、 スレッド状態データ構造体を獲得して、その interp メンバ にアクセスしなければなりません; この処理は、Python が作成した スレッドから行うか、Python を初期化した後で主スレッドから行わねば なりません)。
インタプリタオブジェクトにアクセスできるという仮定の下では、C の スレッドから Python を呼び出す際の典型的な常套句は以下のようになります。
バージョン 2.3 からは、上記の事を全て自動で行われて、 スレッドは PyGILState_*() の恩恵に預かることができます。 C のスレッドから Python を呼び出す典型的な方法は以下のとおりです。
PyGILState_STATE gstate; gstate = PyGILState_Ensure(); /* Perform Python actions here. */ result = CallSomeFunction(); /* evaluate result */ /* Release the thread. No Python API allowed beyond this point. */ PyGILState_Release(gstate);
PyGILState_*()関数は、(Py_Initialize()によって自動的に作られる) グローバルインタプリタ一つだけが存在すると仮定する事に気をつけて下さい。 Python は (Py_NewInterpreter()を使って) 追加のインタプリタを作成できることに 変わりはありませんが、複数インタプリタと PyGILState_*() API を混ぜて 使うことはサポートされていません。
異なるインタプリタに属するスレッド間では、利用可能なメモリ、 開かれているファイルデスクリプタなどといったプロセス状態を除き、 初期状態では何も共有されていません。グローバルインタプリタロック もまた、スレッドがどのインタプリタに属しているかに関わらず すべてのスレッドで共有されています。
) |
PyEval_ReleaseThread(tstate)
といった他のスレッド操作に入るよりも前に呼び出されるようにして
おかなければなりません。
二度目に呼び出すと何も行いません。この関数を Py_Initialize() の前に呼び出しても 安全です。
主スレッドしか存在しないのであれば、ロック操作は必要ありません。 これはよくある状況ですし (ほとんどの Python プログラムはスレッドを 使いません)、ロック操作はインタプリタをごくわずかに低速化します。 従って、初期状態ではロックは生成されません。ロックを使わない状況は、 すでにロックを獲得している状況と同じです: 単一のスレッドしか なければ、オブジェクトへのアクセスは全て安全です。従って、 この関数がロックを初期化すると、同時にロックを獲得するようになって います。Python の thread モジュールは、 新たなスレッドを作成する前に、ロックが存在するか、あるいはまだ 作成されていないかを調べ、PyEval_InitThreads() を 呼び出します。この関数から処理が戻った場合、ロックが作成作成され、呼び出 し元スレッドがそのロックを獲得している事が保証されています。
どのスレッドが現在グローバルインタプリタロックを (存在する場合) 持っているか分からない時にこの関数を使うのは安全では ありません 。
この関数はコンパイル時にスレッドサポートを無効化すると利用できません。
) |
) |
) |
PyThreadState *tstate) |
PyThreadState *tstate) |
) |
PyThreadState *tstate) |
以下のマクロは、通常末尾にセミコロンを付けずに使います; Python ソース配布物内の使用例を見てください。
以下の全ての関数はコンパイル時にスレッドサポートが有効になっている 時だけ利用でき、呼び出すのはインタプリタロックがすでに作成されている 場合だけにしなくてはなりません。
) |
PyInterpreterState *interp) |
PyInterpreterState *interp) |
PyInterpreterState *interp) |
PyThreadState *tstate) |
PyThreadState *tstate) |
) |
PyThreadState *tstate) |
) |
long id, PyObject *exc) |
変更を受けたスレッド状態の数を返します; これは普通は1ですが、スレッドidが 見つからなかった場合は0になります。 もし exc が NULL であれば、そのスレッドで保留されている 例外があればクリアします。 この関数自体は例外を送出しません。 バージョン 2.3 で 新たに追加 された仕様です。
) |
通常、PyGILState_Ensure()呼び出しと PyGILState_Release()呼び出しの間でこれ以外のスレッド関連API を使用することができますが、Release()の前にスレッド状態は復元されていな ければなりません。通常のPy_BEGIN_ALLOW_THREADSマクロと Py_END_ALLOW_THREADSも使用することができます。
戻り値はPyGILState_Acquire()呼び出し時のスレッド状態を隠蔽し た"ハンドル"で、PyGILState_Release()に渡してPythonを同じ状態 に保たなければなりません。再起呼び出しも可能ですが、ハンドルを共有するこ とはできません - それぞれのPyGILState_Ensure呼び出し でハンドルを保存し、対応するPyGILState_Release呼び出しで渡し てください。
関数から復帰したとき、実行中のスレッドはGILを所有しています。処理の失敗 は致命的なエラーです。
バージョン 2.3 で 新たに追加 された仕様です。
PyGILState_STATE) |
PyGILState_Ensure()を呼び出す場合は、必ず同一スレッド内で対 応するPyGILState_Release()を呼び出してください。 バージョン 2.3 で 新たに追加 された仕様です。
ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。