concurrent.interpreters — Mehrere Interpreter im selben Prozess

Hinzugefügt in Version 3.14.

Quellcode: Lib/concurrent/interpreters


Das Modul concurrent.interpreters erstellt Schnittstellen der höheren Ebene auf Basis des Moduls _interpreters auf niedrigerer Ebene.

Das Modul ist hauptsächlich dazu gedacht, eine grundlegende API zur Verwaltung von Interpretern (auch „Sub-Interpretern“ genannt) und zum Ausführen von Code in ihnen bereitzustellen. Das Ausführen beinhaltet hauptsächlich das Umschalten zu einem Interpreter (im aktuellen Thread) und das Aufrufen einer Funktion in diesem Ausführungskontext.

Für Nebenläufigkeit bieten Interpreter selbst (und dieses Modul) nicht viel mehr als Isolation, was allein nicht nützlich ist. Tatsächliche Nebenläufigkeit ist separat über threads verfügbar. Siehe unten.

Siehe auch

InterpreterPoolExecutor

kombiniert Threads mit Interpretern in einer vertrauten Schnittstelle.

Isolierung von Erweiterungsmodulen

wie man ein Erweiterungsmodul für die Unterstützung mehrerer Interpreter aktualisiert

PEP 554

PEP 734

PEP 684

Verfügbarkeit: nicht WASI.

Dieses Modul funktioniert nicht oder ist nicht auf WebAssembly verfügbar. Weitere Informationen finden Sie unter WebAssembly-Plattformen.

Schlüsselleistung

Bevor wir tiefer eintauchen, gibt es eine kleine Anzahl von Details zu beachten, wenn mehrere Interpreter verwendet werden:

  • isoliert, standardmäßig

  • keine impliziten Threads

  • nicht alle PyPI-Pakete unterstützen die Verwendung in mehreren Interpretern noch

Einleitung

Ein „Interpreter“ ist effektiv der Ausführungskontext der Python-Laufzeitumgebung. Er enthält alle Zustände, die die Laufzeitumgebung zum Ausführen eines Programms benötigt. Dazu gehören Dinge wie der Importzustand und die Builtins. (Jeder Thread, auch wenn es nur der Hauptthread ist, hat zusätzliche Laufzeitstatus, zusätzlich zum aktuellen Interpreter, die sich auf die aktuelle Ausnahme und die Bytecode-Ausführungsschleife beziehen.)

Das Konzept und die Funktionalität des Interpreters sind seit Version 2.2 Teil von Python, aber die Funktion war nur über die C-API verfügbar und nicht gut bekannt, und die Isolation war bis Version 3.12 relativ unvollständig.

Mehrere Interpreter und Isolation

Eine Python-Implementierung kann die Verwendung mehrerer Interpreter im selben Prozess unterstützen. CPython hat diese Unterstützung. Jeder Interpreter ist effektiv von den anderen isoliert (mit einer begrenzten Anzahl sorgfältig verwalteter prozessglobaler Ausnahmen von dieser Regel).

Diese Isolation ist hauptsächlich nützlich als starke Trennung zwischen verschiedenen logischen Komponenten eines Programms, bei denen Sie sorgfältig kontrollieren möchten, wie diese Komponenten interagieren.

Hinweis

Interpreter im selben Prozess können technisch niemals streng voneinander isoliert sein, da es nur wenige Einschränkungen für den Speicherzugriff innerhalb desselben Prozesses gibt. Die Python-Laufzeitumgebung bemüht sich nach Kräften um Isolation, aber Erweiterungsmodule können diese leicht verletzen. Verwenden Sie daher keine mehreren Interpreter in sicherheitskritischen Situationen, in denen sie keinen Zugriff auf die Daten der anderen haben sollten.

Ausführen in einem Interpreter

Das Ausführen in einem anderen Interpreter beinhaltet das Umschalten zu ihm im aktuellen Thread und dann das Aufrufen einer Funktion. Die Laufzeitumgebung führt die Funktion mit dem Zustand des aktuellen Interpreters aus. Das Modul concurrent.interpreters bietet eine grundlegende API zum Erstellen und Verwalten von Interpretern sowie die Umschalt-und-Aufruf-Operation.

Für den Vorgang werden keine anderen Threads automatisch gestartet. Dafür gibt es jedoch eine Hilfsfunktion. Es gibt eine weitere spezielle Hilfsfunktion zum Aufrufen der integrierten Funktion exec() in einem Interpreter.

Wenn exec() (oder eval()) in einem Interpreter aufgerufen werden, werden sie mit dem Modul __main__ des Interpreters als „globals“-Namensraum ausgeführt. Dasselbe gilt für Funktionen, die keinem Modul zugeordnet sind. Dies ist dasselbe wie die Ausführung von Skripten, die über die Kommandozeile aufgerufen werden, im Modul __main__.

Nebenläufigkeit und Parallelität

Wie bereits erwähnt, bieten Interpreter allein keine Nebenläufigkeit. Sie stellen strikt den isolierten Ausführungskontext dar, den die Laufzeitumgebung *im aktuellen Thread* verwendet. Diese Isolation macht sie Prozess-ähnlich, aber sie genießen dennoch In-Prozess-Effizienz, ähnlich wie Threads.

Allerdings unterstützen Interpreter von Natur aus bestimmte Formen der Nebenläufigkeit. Es gibt einen mächtigen Nebeneffekt dieser Isolation. Sie ermöglicht einen anderen Ansatz zur Nebenläufigkeit als bei Async oder Threads. Es ist ein Nebenläufigkeitsmodell, das dem CSP- oder Actor-Modell ähnelt und relativ leicht zu verstehen ist.

Sie können dieses Nebenläufigkeitsmodell in einem einzelnen Thread nutzen und zwischen Interpretern hin- und herschalten, Stackless-Stil. Dieses Modell ist jedoch nützlicher, wenn Sie Interpreter mit mehreren Threads kombinieren. Dies beinhaltet hauptsächlich das Starten eines neuen Threads, in dem Sie zu einem anderen Interpreter wechseln und dort das Gewünschte ausführen.

Jeder tatsächliche Thread in Python, auch wenn Sie nur im Hauptthread laufen, hat seinen eigenen *aktuellen* Ausführungskontext. Mehrere Threads können denselben oder unterschiedliche Interpreter verwenden.

Auf hoher Ebene können Sie die Kombination von Threads und Interpretern als Threads mit optionalem Sharing betrachten.

Als erheblicher Bonus sind Interpreter ausreichend isoliert, dass sie die GIL nicht teilen. Das bedeutet, dass die Kombination von Threads mit mehreren Interpretern volle Multi-Core-Parallelität ermöglicht. (Dies ist seit Python 3.12 der Fall.)

Kommunikation zwischen Interpretern

In der Praxis sind mehrere Interpreter nur dann nützlich, wenn wir eine Möglichkeit zur Kommunikation zwischen ihnen haben. Dies beinhaltet normalerweise eine Form der Nachrichtenübermittlung, kann aber auch das Teilen von Daten auf eine sorgfältig verwaltete Weise bedeuten.

Mit diesem Gedanken im Hinterkopf bietet das Modul concurrent.interpreters eine queue.Queue-Implementierung, die über create_queue() verfügbar ist.

Objekte „teilen“

Alle Daten, die tatsächlich zwischen Interpretern geteilt werden, verlieren die vom GIL bereitgestellte Thread-Sicherheit. Es gibt verschiedene Optionen für den Umgang damit in Erweiterungsmodulen. Aus Python-Code bedeutet der Mangel an Thread-Sicherheit jedoch, dass Objekte nicht wirklich geteilt werden können, mit wenigen Ausnahmen. Stattdessen muss eine Kopie erstellt werden, was bedeutet, dass mutable Objekte nicht synchron bleiben.

Standardmäßig werden die meisten Objekte mit pickle kopiert, wenn sie an einen anderen Interpreter übergeben werden. Fast alle unveränderlichen eingebauten Objekte werden entweder direkt geteilt oder effizient kopiert. Zum Beispiel:

Es gibt eine kleine Anzahl von Python-Typen, die tatsächlich mutable Daten zwischen Interpretern teilen.

Referenz

Dieses Modul definiert die folgenden Funktionen

concurrent.interpreters.list_all()

Gibt eine Liste von Interpreter-Objekten zurück, eines für jeden vorhandenen Interpreter.

concurrent.interpreters.get_current()

Gibt ein Interpreter-Objekt für den aktuell laufenden Interpreter zurück.

concurrent.interpreters.get_main()

Gibt ein Interpreter-Objekt für den Hauptinterpreter zurück. Dies ist der Interpreter, den die Laufzeitumgebung zum Ausführen der REPL oder des auf der Kommandozeile angegebenen Skripts erstellt hat. Er ist normalerweise der einzige.

concurrent.interpreters.create()

Initialisiert einen neuen (inaktiven) Python-Interpreter und gibt ein Interpreter-Objekt dafür zurück.

concurrent.interpreters.create_queue()

Initialisiert eine neue Interpreter-übergreifende Warteschlange und gibt ein Queue-Objekt dafür zurück.

Interpreter-Objekte

class concurrent.interpreters.Interpreter(id)

Ein einzelner Interpreter im aktuellen Prozess.

Im Allgemeinen sollte Interpreter nicht direkt aufgerufen werden. Verwenden Sie stattdessen create() oder eine der anderen Modulfunktionen.

id

(nur lesbar)

Die ID des zugrundeliegenden Interpreters.

whence

(nur lesbar)

Ein String, der beschreibt, woher der Interpreter stammt.

is_running()

Gibt True zurück, wenn der Interpreter gerade Code in seinem __main__-Modul ausführt, und andernfalls False.

close()

Finalisiert und zerstört den Interpreter.

prepare_main(ns=None, **kwargs)

Bindet Objekte im __main__-Modul des Interpreters.

Einige Objekte werden tatsächlich geteilt und einige werden effizient kopiert, aber die meisten werden über pickle kopiert. Siehe „Sharing“ Objects.

exec(code, /, dedent=True)

Führt den gegebenen Quellcode im Interpreter aus (im aktuellen Thread).

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

Gibt das Ergebnis des Aufrufs der gegebenen Funktion im Interpreter zurück (im aktuellen Thread).

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

Führt die gegebene Funktion im Interpreter aus (in einem neuen Thread).

Ausnahmen

exception concurrent.interpreters.InterpreterError

Diese Ausnahme, eine Unterklasse von Exception, wird ausgelöst, wenn ein Interpreter-bezogener Fehler auftritt.

exception concurrent.interpreters.InterpreterNotFoundError

Diese Ausnahme, eine Unterklasse von InterpreterError, wird ausgelöst, wenn der Zielinterpreter nicht mehr existiert.

exception concurrent.interpreters.ExecutionFailed

Diese Ausnahme, eine Unterklasse von InterpreterError, wird ausgelöst, wenn der ausgeführte Code eine unbehandelte Ausnahme ausgelöst hat.

excinfo

Ein grundlegender Schnappschuss der im anderen Interpreter ausgelösten Ausnahme.

exception concurrent.interpreters.NotShareableError

Diese Ausnahme, eine Unterklasse von TypeError, wird ausgelöst, wenn ein Objekt nicht an einen anderen Interpreter gesendet werden kann.

Kommunikation zwischen Interpretern

class concurrent.interpreters.Queue(id)

Ein Wrapper um eine Low-Level-Interpreter-übergreifende Warteschlange, die die Schnittstelle queue.Queue implementiert. Die zugrundeliegende Warteschlange kann nur über create_queue() erstellt werden.

Einige Objekte werden tatsächlich geteilt und einige werden effizient kopiert, aber die meisten werden über pickle kopiert. Siehe „Sharing“ Objects.

id

(nur lesbar)

Die ID der Warteschlange.

exception concurrent.interpreters.QueueEmptyError

Diese Ausnahme, eine Unterklasse von queue.Empty, wird von Queue.get() und Queue.get_nowait() ausgelöst, wenn die Warteschlange leer ist.

exception concurrent.interpreters.QueueFullError

Diese Ausnahme, eine Unterklasse von queue.Full, wird von Queue.put() und Queue.put_nowait() ausgelöst, wenn die Warteschlange voll ist.

Grundlegende Verwendung

Erstellen eines Interpreters und Ausführen von Code darin

from concurrent import interpreters

interp = interpreters.create()

# Run in the current OS thread.

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)

# Run in new OS thread.

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