8.1 スレッド状態 (thread state) とグローバルインタプリタロック (global interpreter lock)

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 を呼び出す際の典型的な常套句は以下のようになります。

    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);

PyInterpreterState
このデータ構造体は、協調動作する多数のスレッド間で共有されている 状態 (state) を表現します。同じインタプリタに属するスレッドは モジュール管理情報やその他いくつかの内部的な情報を共有しています。 この構造体には公開 (public) のメンバはありません。

異なるインタプリタに属するスレッド間では、利用可能なメモリ、 開かれているファイルデスクリプタなどといったプロセス状態を除き、 初期状態では何も共有されていません。グローバルインタプリタロック もまた、スレッドがどのインタプリタに属しているかに関わらず すべてのスレッドで共有されています。

PyThreadState
単一のスレッドの状態を表現する表現するデータ構造体です。 データメンバ PyInterpreterState *interp だけが公開されていて、スレッドのインタプリタ状態を指すポインタに なっています。

void PyEval_InitThreads()
グローバルインタプリタロックを初期化し、獲得します。 この関数は、主スレッドが第二のスレッドを生成する以前や、 PyEval_ReleaseLock()PyEval_ReleaseThread(tstate) といった他のスレッド操作に入るよりも前に呼び出されるようにして おかなければなりません。

二度目に呼び出すと何も行いません。この関数を Py_Initialize() の前に呼び出しても 安全です。

主スレッドしか存在しないのであれば、ロック操作は必要ありません。 これはよくある状況ですし (ほとんどの Python プログラムはスレッドを 使いません)、ロック操作はインタプリタをごくわずかに低速化します。 従って、初期状態ではロックは生成されません。ロックを使わない状況は、 すでにロックを獲得している状況と同じです: 単一のスレッドしか なければ、オブジェクトへのアクセスは全て安全です。従って、 この関数がロックを初期化すると、同時にロックを獲得するようになって います。Python の thread モジュールは、 新たなスレッドを作成する前に、ロックが存在するか、あるいはまだ 作成されていないかを調べ、PyEval_InitThreads() を 呼び出します。この関数から処理が戻った場合、ロックが作成作成され、呼び出 し元スレッドがそのロックを獲得している事が保証されています。

どのスレッドが現在グローバルインタプリタロックを (存在する場合) 持っているか分からない時にこの関数を使うのは安全では ありません

この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

int PyEval_ThreadsInitialized()
PyEval_InitThreads()をすでに呼び出している場合は真 (非ゼロ) を返します。この関数は、ロックを獲得せずに呼び出すことができますので、シ ングルスレッドで実行している場合にはロック関連のAPI呼び出しを避けるため に使うことができます。 この関数はコンパイル時にスレッドサポートを無効化すると利用できません。 バージョン 2.4 で 新たに追加 された仕様です。

void PyEval_AcquireLock()
グローバルインタプリタロックを獲得します。 ロックは前もって作成されていなければなりません。 この関数を呼び出したスレッドがすでにロックを獲得している場合、 デッドロックに陥ります。 この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

void PyEval_ReleaseLock()
グローバルインタプリタロックを解放します。 ロックは前もって作成されていなければなりません。 この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

void PyEval_AcquireThread(PyThreadState *tstate)
グローバルインタプリタロックを獲得し、現在のスレッド状態を tstate に設定します。tstateNULLであっては なりません。ロックはあらかじめ作成されていなければなりません。 この関数を呼び出したスレッドがすでにロックを獲得している場合、 デッドロックに陥ります。 この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

void PyEval_ReleaseThread(PyThreadState *tstate)
現在のスレッド状態をリセットして NULL にし、グローバルインタプリタ ロックを解放します。ロックはあらかじめ作成されていなければならず、 かつ現在のスレッドが保持していなければなりません。tstateNULLであってはなりませんが、その値が現在のスレッド状態を 表現しているかどうかを調べるためにだけ使われます -- もしそうで なければ、致命的エラーが報告されます。 この関数はコンパイル時にスレッドサポートを無効化すると利用できません。

PyThreadState* PyEval_SaveThread()
(インタプリタロックが生成されていて、スレッドサポートが有効の 場合) インタプリタロックを解放して、スレッド状態を NULLにし、 以前のスレッド状態 (NULLにはなりません) を返します。 ロックがすでに生成されている場合、現在のスレッドがロックを獲得 していなければなりません。

void PyEval_RestoreThread(PyThreadState *tstate)
(インタプリタロックが生成されていて、スレッドサポートが有効の 場合) インタプリタロックを獲得して、現在のスレッド状態を tstate に設定します。tstateNULLであっては なりません。 この関数を呼び出したスレッドがすでにロックを獲得している場合、 デッドロックに陥ります。 (この関数はコンパイル時にスレッドサポートを無効化すると利用できません。)

以下のマクロは、通常末尾にセミコロンを付けずに使います; Python ソース配布物内の使用例を見てください。

Py_BEGIN_ALLOW_THREADS
このマクロを展開すると "{ PyThreadState *_save; _save = PyEval_SaveThread();"になります。 マクロに開き波括弧が入っていることに注意してください; この波括弧は 後で Py_END_ALLOW_THREADS マクロと対応させなければ なりません。 マクロについての詳しい議論は上記を参照してください。 コンパイル時にスレッドサポートが無効化されていると何も行いません。

Py_END_ALLOW_THREADS
このマクロを展開すると "PyEval_RestoreThread(_save); }"になります。 マクロに開き波括弧が入っていることに注意してください; この波括弧は 事前の Py_BEGIN_ALLOW_THREADS マクロと対応して いなければなりません。 マクロについての詳しい議論は上記を参照してください。 コンパイル時にスレッドサポートが無効化されていると何も行いません。

Py_BLOCK_THREADS
このマクロを展開すると "PyEval_RestoreThread(_save);"になります: 閉じ波括弧のないPy_END_ALLOW_THREADS と同じです。 コンパイル時にスレッドサポートが無効化されていると何も行いません。

Py_UNBLOCK_THREADS
このマクロを展開すると "_save = PyEval_SaveThread();"になります: 閉じ波括弧のないPy_BEGIN_ALLOW_THREADS と同じです。 コンパイル時にスレッドサポートが無効化されていると何も行いません。

以下の全ての関数はコンパイル時にスレッドサポートが有効になっている 時だけ利用でき、呼び出すのはインタプリタロックがすでに作成されている 場合だけにしなくてはなりません。

PyInterpreterState* PyInterpreterState_New()
新しいインタプリタ状態オブジェクトを生成します。 インタプリタロックを保持しておく必要はありませんが、この関数を次々に 呼び出す必要がある場合には保持しておいたほうがよいでしょう。

void PyInterpreterState_Clear(PyInterpreterState *interp)
インタプリタ状態オブジェクト内の全ての情報をリセットします。 インタプリタロックを保持していなければなりません。

void PyInterpreterState_Delete(PyInterpreterState *interp)
インタプリタ状態オブジェクトを破壊します。 インタプリタロックを保持しておく必要はありません。 インタプリタ状態はPyInterpreterState_Clear() であらかじめ リセットしておかなければなりません。

PyThreadState* PyThreadState_New(PyInterpreterState *interp)
指定したインタプリタオブジェクトに属する新たなスレッド状態オブジェクトを 生成します。 インタプリタロックを保持しておく必要はありませんが、この関数を次々に 呼び出す必要がある場合には保持しておいたほうがよいでしょう。

void PyThreadState_Clear(PyThreadState *tstate)
スレッド状態オブジェクト内の全ての情報をリセットします。 インタプリタロックを保持していなければなりません。

void PyThreadState_Delete(PyThreadState *tstate)
スレッド状態オブジェクトを破壊します。 インタプリタロックを保持していなければなりません。 スレッド状態はPyThreadState_Clear() であらかじめ リセットしておかなければなりません。

PyThreadState* PyThreadState_Get()
現在のスレッド状態を返します。 インタプリタロックを保持していなければなりません。 現在のスレッド状態が NULLなら、(呼び出し側が NULLチェックを しなくてすむように) この関数は致命的エラーを起こすようになっています。

PyThreadState* PyThreadState_Swap(PyThreadState *tstate)
現在のスレッド状態を tstate に指定したスレッド状態と入れ変えます。 tstateNULLであってはなりません。 インタプリタロックを保持していなければなりません。

PyObject* PyThreadState_GetDict()
戻り値: 借りた参照.
拡張モジュールがスレッド固有の状態情報を保存できるような辞書を返します。 各々の拡張モジュールが辞書に状態情報を保存するためには唯一のキーを 使わねばなりません。 現在のスレッド状態がない時にこの関数を呼び出してもかまいません。 この関数が NULLを返す場合、例外はまったく送出されず、呼び出し側は 現在のスレッド状態が利用できないと考えねばなりません。 バージョン 2.3 で 変更 された仕様: 以前は、現在のスレッドがアクティブなときのみ呼び出せる ようになっており、 NULL は例外が送出されたことを意味していました

int PyThreadState_SetAsyncExc(long id, PyObject *exc)
スレッド内で非同期的に例外を送出します。 id 引数はターゲットとなるスレッドのスレッド id です; exc は送出する例外オブジェクトです。 この関数は exc に対する参照を一切盗み取りません。 素朴な間違いを防ぐため、この関数を呼び出すには独自に C 拡張モジュール を書かねばなりません。 グローバルインタプリタロックを保持した状態で呼び出さなければなりません。 変更を受けたスレッド状態の数を返します; 1 よりも大きな数を返した場合、 何らかのトラブルに巻き込まれていることになり、excNULL にして再度呼び出すことで効果を打ち消さねばなりません。 この関数自体は例外を送出しません。 バージョン 2.3 で 新たに追加 された仕様です。

PyGILState_STATE PyGILState_Ensure()
Pythonの状態やスレッドロックに関わらず、実行中スレッドでPython C APIの呼 び出しが可能となるようにします。この関数はスレッド内で何度でも呼び出すこ とができますが、必ず全ての呼び出しに対応して PyGILState_Release()を呼び出す必要があります。

通常、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 で 新たに追加 された仕様です。

void PyGILState_Release(PyGILState_STATE)
獲得したすべてのリソースを開放します。この関数を呼び出すと、Pythonの状態 は対応するPyGILState_Ensureを呼び出す前と同じとなります。(通 常、この状態は呼び出し元でははわかりませんので、GILState APIを利用するよ うにしてください。)

PyGILState_Ensure()を呼び出す場合は、必ず同一スレッド内で対 応するPyGILState_Release()を呼び出してください。 バージョン 2.3 で 新たに追加 された仕様です。

ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。