concurrent.interpreters --- 同じプロセス内の複数のインタープリタ

Added in version 3.14.

ソースコード: Lib/concurrent/interpreters


concurrent.interpreters モジュールは、低水準の _interpreters モジュールの上に、より高水準のインターフェースを構築します。

このモジュールの主な目的は、インタープリタ (「サブインタープリタ」とも呼ばれます) の管理と、その中での実行のための基本的な API を提供することです。 実行時には主に、現在のスレッドでインタープリタを切り替え、その実行コンテキストで関数を呼び出します。

並行処理に関しては、インタープリタ自体 (およびこのモジュール) が提供するのはほぼ分離だけであり、それだけでは役に立ちません。 実際の並行処理は、別途 スレッド によって利用できます。 後述の説明 を参照してください。

参考

InterpreterPoolExecutor

使い慣れたインターフェースで、スレッドとインタープリタを組み合わせます。

拡張モジュールを分離する

複数のインタープリタをサポートするように拡張モジュールを更新する方法。

PEP 554

PEP 734

PEP 684

利用可能な環境: not WASI.

このモジュールは WebAssembly では動作しないか、利用不可です。詳しくは、WebAssembly プラットフォーム を見てください。

主な注意点

詳しい説明に入る前に、複数のインタープリタの使用について留意すべき点を挙げます:

  • デフォルトで 分離される

  • 暗黙にスレッドは作成されない

  • PyPI のすべてのパッケージが、複数のインタープリタでの使用をサポートしているわけではない

はじめに

「インタープリタ」は、実質的に Python ランタイムの実行コンテキストです。 プログラムの実行に必要な状態をすべて含んでいます。 これには、インポートの状態や組み込みオブジェクトなどが含まれます。 (メインスレッドしかない場合でも、各スレッドには現在のインタープリタの状態に加え、現在の例外やバイトコードの評価ループに関する実行時の状態があります)。

The concept and functionality of the interpreter have been a part of Python since version 2.2, but the feature was only available through the C-API and not well known, and the isolation was relatively incomplete until version 3.12.

複数のインタープリタと分離

Python の実装は、同じプロセス内で複数のインタープリタを使うことをサポートする場合があります。 CPython はこれをサポートしています。 各インタープリタは、他のインタープリタから実質的に分離されています (プロセス全体で共有されるものに、慎重に管理された少数の例外があります)。

この分離は主に、プログラムを構成する別々の論理的な部分を明確に切り離し、それらのやり取りを慎重に制御したい場合に役立ちます。

注釈

同じプロセス内ではメモリアクセスに対する制限がほとんどないため、技術的には、同じプロセス内のインタープリタを互いに厳密に分離することはできません。 Python ランタイムは可能な限り分離しますが、拡張モジュールは容易にその分離を破ることができます。 したがって、互いのデータにアクセスできてはならないような、セキュリティが重要な状況では、複数のインタープリタを使わないでください。

インタープリタ内での実行

別のインタープリタ内で実行するには、現在のスレッドでそのインタープリタに切り替えてから、関数を呼び出します。 ランタイムは、現在のインタープリタの状態を使って関数を実行します。 concurrent.interpreters モジュールは、インタープリタを作成・管理する基本的な API と、この切り替えと呼び出しの操作を提供します。

この操作で、他のスレッドが自動的に開始されることはありません。 ただし、そのための 補助機能 があります。 インタープリタ内で組み込みの exec() を呼び出すための専用の補助機能もあります。

インタープリタ内で exec() (または eval()) を呼び出すと、そのインタープリタの __main__ モジュールを「グローバル」名前空間として実行します。 どのモジュールにも関連付けられていない関数も同様です。 これは、コマンドラインから起動したスクリプトが __main__ モジュール内で動作するのと同じです。

並行処理と並列処理

前述のとおり、インタープリタ自体は並行処理を提供しません。 厳密には、ランタイムが 現在のスレッドで 使う、分離された実行コンテキストを表すものです。 その分離はプロセスに似ていますが、スレッドと同様に、同じプロセス内にあることによる効率のよさも得られます。

とはいえ、インタープリタは特定の形態の並行処理を自然にサポートします。 分離には強力な副次効果があり、async やスレッドとは異なる並行処理の手法が可能になります。 その並行処理モデルは CSP やアクターモデルに似ており、比較的理解しやすいものです。

単一のスレッドでも、Stackless のようにインタープリタを行き来して、この並行処理モデルを活用できます。 しかし、このモデルはインタープリタと複数のスレッドを組み合わせたときに、より役立ちます。 主な手順は、新しいスレッドを起動し、その中で別のインタープリタに切り替えて、必要な処理を実行することです。

メインスレッドだけで実行している場合でも、Python の各実スレッドには、それぞれの 現在の 実行コンテキストがあります。 複数のスレッドで同じインタープリタを使うことも、別々のインタープリタを使うこともできます。

大まかには、スレッドとインタープリタの組み合わせを、明示的に選んだものだけを共有するスレッドと考えることができます。

大きな利点として、インタープリタは GIL を共有しないほど十分に分離されています。 そのため、スレッドと複数のインタープリタを組み合わせると、複数コアでの完全な並列実行が可能になります。 (Python 3.12 以降はこのようになっています)。

インタープリタ間の通信

実際には、複数のインタープリタが役立つのは、それらの間で通信する手段がある場合だけです。 通常は何らかのメッセージパッシングを使いますが、慎重に管理した方法でデータを共有する場合もあります。

このため、concurrent.interpreters モジュールは queue.Queue の実装を提供しており、create_queue() で利用できます。

オブジェクトの「共有」

インタープリタ間で実際に共有されるデータは、GIL が提供するスレッドセーフ性を失います。 拡張モジュールでは、これに対処するための選択肢がいくつかあります。 しかし、Python コードでは、スレッドセーフ性がないため、ごく一部の例外を除いてオブジェクトを実際に共有することはできません。 代わりにコピーを作成しなければならないため、ミュータブルなオブジェクトの同期は保たれません。

デフォルトでは、ほとんどのオブジェクトは別のインタープリタに渡す際に pickle でコピーされます。 イミュータブルな組み込みオブジェクトのほぼすべては、直接共有されるか、効率よくコピーされます。 例えば、次のものがあります:

インタープリタ間で実際にミュータブルなデータを共有する Python の型も、少数ですが存在します:

リファレンス

このモジュールは以下の関数を定義しています:

concurrent.interpreters.list_all()

既存の各インタープリタに対応する Interpreter オブジェクトの list を返します。

concurrent.interpreters.get_current()

現在実行中のインタープリタの Interpreter オブジェクトを返します。

concurrent.interpreters.get_main()

メインインタープリタの Interpreter オブジェクトを返します。 これは、REPL またはコマンドラインで指定されたスクリプトを実行するために、ランタイムが作成したインタープリタです。 通常は、これが唯一のインタープリタです。

concurrent.interpreters.create()

新しい (実行中でない) Python インタープリタを初期化し、その Interpreter オブジェクトを返します。

concurrent.interpreters.create_queue(maxsize=0)

インタープリタ間で使う新しいキューを初期化し、その Queue オブジェクトを返します。

maxsize は、キューに入れられる要素数の上限を設定する整数です。 このサイズに達すると、キューの要素が取り出されるまで挿入はブロックされます。 maxsize が 0 以下の場合、キューのサイズに上限はありません。

インタープリタオブジェクト

class concurrent.interpreters.Interpreter(id)

現在のプロセス内の一つのインタープリタです。

通常、Interpreter を直接呼び出すべきではありません。 代わりに create() または他のモジュール関数を使ってください。

id

(読み出し専用)

基となるインタープリタの ID です。

whence

(読み出し専用)

インタープリタがどこから作成されたかを表す文字列です。

is_running()

インタープリタが現在 __main__ モジュール内でコードを実行している場合は True、それ以外の場合は False を返します。

close()

インタープリタの終了処理を行い、破棄します。

prepare_main(ns=None, **kwargs)

インタープリタの __main__ モジュール内にオブジェクトを束縛します。

一部のオブジェクトは実際に共有され、効率よくコピーされるものもありますが、ほとんどは pickle を介してコピーされます。 オブジェクトの「共有」 を参照してください。

exec(code, /)

指定されたソースコードを、現在のスレッドでそのインタープリタ内で実行します。

call(callable, /, *args, **kwargs)

指定された関数を、現在のスレッドでそのインタープリタ内で実行した結果を返します。

call_in_thread(callable, /, *args, **kwargs)

指定された関数を、新しいスレッドでそのインタープリタ内で実行します。

例外

exception concurrent.interpreters.InterpreterError

Exception のサブクラスで、インタープリタに関するエラーが発生した場合に送出されます。

exception concurrent.interpreters.InterpreterNotFoundError

InterpreterError のサブクラスで、対象のインタープリタがすでに存在しない場合に送出されます。

exception concurrent.interpreters.ExecutionFailed

InterpreterError のサブクラスで、実行中のコードが捕捉されない例外を送出した場合に送出されます。

excinfo

別のインタープリタで送出された例外の基本的なスナップショットです。

exception concurrent.interpreters.NotShareableError

TypeError のサブクラスで、オブジェクトを別のインタープリタに送れない場合に送出されます。

インタープリタ間での通信

class concurrent.interpreters.Queue(id)

インタープリタ間で使う低水準のキューのラッパです。 queue.Queue のメソッドのうち、task_done()、join()、shutdown() を除くすべてを実装しています。 基となるキューを作成できるのは create_queue() だけです。

一部のオブジェクトは実際に共有され、効率よくコピーされるものもありますが、ほとんどは pickle を介してコピーされます。 オブジェクトの「共有」 を参照してください。

id

(読み出し専用)

キューの ID です。

qsize()

キュー内の要素数を返します。

empty()

キューが空ならば True を、そうでなければ False を返します。

full()

キューがいっぱいの場合は True、それ以外の場合は False を返します。

put(obj, block=True, timeout=None)

obj をキューに入れます。 オプション引数 block が真で、timeout が None (デフォルト) の場合、必要に応じて空きができるまでブロックします。 timeout が正の数の場合、最大 timeout 秒ブロックし、その間に空きができなければ QueueFull 例外を送出します。 それ以外の場合 (block が偽の場合)、直ちに利用できる空きがあればオブジェクトをキューに入れ、なければ QueueFull 例外を送出します (この場合、timeout は無視されます)。

put_nowait(obj)

put(obj, block=False) と等価です。

get(block=True, timeout=None)

キューからオブジェクトを取り出して返します。 オプション引数 block が真で、timeout が None (デフォルト) の場合、必要に応じてオブジェクトが利用できるまでブロックします。 timeout が正の数の場合、最大 timeout 秒ブロックし、その間にオブジェクトが利用できなければ QueueEmpty 例外を送出します。 それ以外の場合 (block が偽の場合)、直ちに利用できるオブジェクトがあればそれを返し、なければ QueueEmpty 例外を送出します (この場合、timeout は無視されます)。

get_nowait()

get(block=False) と等価です。

exception concurrent.interpreters.QueueEmpty

queue.Empty のサブクラスで、キューが空の場合に Queue.get() と Queue.get_nowait() から送出されます。

exception concurrent.interpreters.QueueFull

queue.Full のサブクラスで、キューがいっぱいの場合に Queue.put() と Queue.put_nowait() から送出されます。

基本的な使い方

インタープリタを作成し、その中でコードを実行する例。

from concurrent import interpreters

interp = interpreters.create()

# 現在の OS スレッドで実行する

interp.exec('print("spam!")')

interp.exec("""if True:
    print('spam!')
    """)

from textwrap import dedent
interp.exec(dedent("""
    print('spam!')
    """))

def run(arg):
    return arg

res = interp.call(run, 'spam!')
print(res)

def run():
    print('spam!')

interp.call(run)

# 新しい OS スレッドで実行する

t = interp.call_in_thread(run)
t.join()

キューを使ってインタープリタ間で通信する例。

from concurrent import interpreters

interp = interpreters.create()
queue = interpreters.create_queue()

def worker(queue):
    queue.put('spam!')

t = interp.call_in_thread(worker, queue)
print(queue.get())
t.join()