web-dev-qa-db-de.com

Was ist die globale Interpretersperre (GIL) in CPython?

Was ist eine globale Interpretersperre und warum ist dies ein Problem?

Beim Entfernen der GIL aus Python wurde viel Lärm gemacht, und ich möchte verstehen, warum das so wichtig ist. Ich habe selbst noch nie einen Compiler oder Interpreten geschrieben. Seien Sie also nicht sparsam mit Details. Ich werde sie wahrscheinlich zum Verstehen brauchen.

221
e-satis

Pythons GIL soll den Zugriff auf Interpreter-Interna aus verschiedenen Threads serialisieren. Auf Mehrkernsystemen bedeutet dies, dass mehrere Threads nicht effektiv mehrere Kerne verwenden können. (Wenn die GIL nicht zu diesem Problem geführt hätte, würden sich die meisten Menschen nicht um die GIL kümmern - sie wird nur als Problem aufgeworfen, da immer mehr Multi-Core-Systeme zum Einsatz kommen.) Wenn Sie sie im Detail verstehen möchten, Sie können dieses Video oder diese Folien ansehen. Es könnte zu viele Informationen geben, aber dann haben Sie nach Details gefragt :-)

Beachten Sie, dass Pythons GIL nur für CPython, die Referenzimplementierung, ein Problem darstellt. Jython und IronPython haben keine GIL. Als Python Entwickler stoßen Sie im Allgemeinen nicht auf die GIL, es sei denn, Sie schreiben eine C-Erweiterung. C-Erweiterungsschreiber müssen die GIL freigeben, wenn ihre Erweiterungen E/A blockieren dass andere Threads im Prozess Python eine Chance bekommen, ausgeführt zu werden.

204
Vinay Sajip

Angenommen, Sie haben mehrere Threads, die sich nicht wirklich gegenseitig berühren . Diese sollten so unabhängig wie möglich ablaufen. Wenn Sie eine "globale Sperre" haben, die Sie erwerben müssen, um (sagen wir) eine Funktion aufzurufen, kann dies zu einem Engpass führen. Es kann sein, dass Sie nicht viel davon profitieren, wenn Sie mehrere Threads haben.

Um es in eine reale Analogie umzusetzen: Stellen Sie sich 100 Entwickler vor, die in einem Unternehmen mit nur einer einzigen Kaffeetasse arbeiten. Die meisten Entwickler würden ihre Zeit damit verbringen, auf Kaffee zu warten, anstatt zu programmieren.

Nichts davon ist Python-spezifisch - ich weiß nicht genau, wofür Python eine GIL benötigt. Hoffentlich haben Sie eine bessere Vorstellung von dem allgemeinen Konzept.

55
Jon Skeet

Lassen Sie uns zuerst verstehen, was die python GIL liefert:

Jede Operation/Anweisung wird im Interpreter ausgeführt. GIL stellt sicher, dass der Interpreter zu einem bestimmten Zeitpunkt von einem einzelnen Thread gehalten wird. Und Ihr python Programm mit mehreren Threads funktioniert in einem einzigen Interpreter. Zu einem bestimmten Zeitpunkt wird dieser Interpreter von einem einzigen Thread gehalten. Dies bedeutet, dass nur der Thread ist, der den Interpreter hält Laufen um jederzeit.

Warum ist das ein Problem:

Ihr Computer verfügt möglicherweise über mehrere Kerne/Prozessoren. Und mehrere Kerne ermöglichen die Ausführung mehrerer Threads gleichzeitig, d. H., Mehrere Threads könnten zu einem bestimmten Zeitpunkt ausführen.. Da der Interpreter jedoch von einem einzelnen Thread gehalten wird, tun andere Threads nichts, obwohl sie Zugriff auf einen Core haben. Sie erhalten also keinen Vorteil durch mehrere Kerne, da zu jedem Zeitpunkt nur ein einzelner Kern verwendet wird, der der Kern ist, der von dem Thread verwendet wird, der derzeit den Interpreter enthält. Die Ausführung Ihres Programms dauert also so lange, als wäre es ein einzelnes Thread-Programm.

Möglicherweise blockierende oder lang andauernde Vorgänge wie E/A, Bildverarbeitung und NumPy-Zahlenverarbeitung treten jedoch außerhalb der GIL auf. Entnommen aus hier . Für solche Operationen ist eine Multithread-Operation trotz der Anwesenheit von GIL immer noch schneller als eine Single-Thread-Operation. GIL ist also nicht immer ein Engpass.

Edit: GIL ist ein Implementierungsdetail von CPython. IronPython und Jython haben kein GIL, daher sollte ein echtes Multithread-Programm in ihnen möglich sein, obwohl ich PyPy und Jython noch nie verwendet habe und mir dessen nicht sicher bin.

31
Akshar Raaj

Python erlaubt kein Multithreading im wahrsten Sinne des Wortes. Es hat ein Multithreading-Paket, aber wenn Sie den Code mit mehreren Threads beschleunigen möchten, ist es normalerweise keine gute Idee, es zu verwenden. Python hat ein Konstrukt namens Global Interpreter Lock (GIL).

https://www.youtube.com/watch?v=ph374fJqFPE

Die GIL stellt sicher, dass immer nur einer Ihrer Threads ausgeführt werden kann. Ein Thread erwirbt die GIL, erledigt ein wenig Arbeit und leitet sie dann an den nächsten Thread weiter. Dies geschieht sehr schnell, so dass es für das menschliche Auge so aussieht, als würden Ihre Threads parallel ausgeführt, aber sie wechseln sich wirklich nur mit demselben CPU-Kern ab. All diese GIL-Übergabe erhöht den Aufwand für die Ausführung. Dies bedeutet, dass die Verwendung des Threading-Pakets oft keine gute Idee ist, wenn Sie den Code schneller ausführen möchten.

Es gibt Gründe, das Threading-Paket von Python zu verwenden. Wenn Sie einige Dinge gleichzeitig ausführen möchten und die Effizienz keine Rolle spielt, ist dies in Ordnung und praktisch. Oder wenn Sie Code ausführen, der auf etwas warten muss (z. B. auf E/A), kann dies sehr sinnvoll sein. Mit der Threading-Bibliothek können Sie jedoch keine zusätzlichen CPU-Kerne verwenden.

Multi-Threading kann an das Betriebssystem ausgelagert werden (durch Multi-Processing), eine externe Anwendung, die Ihren Python Code aufruft (zB Spark oder Hadoop) ) oder einen Code, den Ihr Python Code aufruft (zB: Sie könnten Ihren Python Code eine C-Funktion aufrufen lassen, die das teure Multithreading erledigt) .

16
Ijaz Ahmad Khan

Immer wenn zwei Threads Zugriff auf dieselbe Variable haben, haben Sie ein Problem. In C++ besteht die Möglichkeit, das Problem zu vermeiden, darin, eine Mutex-Sperre zu definieren, um zu verhindern, dass zwei Threads gleichzeitig den Setter eines Objekts eingeben.

Multithreading ist in Python möglich, aber zwei Threads können nicht gleichzeitig mit einer genaueren Granularität als einer python= Anweisung ausgeführt werden. Der laufende Thread erhält eine globale Sperre mit dem Namen GIL.

Dies bedeutet, dass sich Ihre Leistung nicht verbessert, wenn Sie anfangen, Multithread-Code zu schreiben, um die Vorteile Ihres Multicore-Prozessors zu nutzen. Die übliche Problemumgehung besteht darin, mehrere Prozesse gleichzeitig auszuführen.

Beachten Sie, dass es möglich ist, die GIL freizugeben, wenn Sie sich in einer Methode befinden, die Sie beispielsweise in C geschrieben haben.

Die Verwendung einer GIL ist nicht inhärent für Python, sondern für einige ihrer Interpreter, einschließlich des gängigsten CPython. (#Bearbeitet, siehe Kommentar)

Das GIL-Problem ist in Python 3000 immer noch gültig.

14
fulmicoton

Python 3.7-Dokumentation

Ich möchte auch das folgende Zitat aus der Python threading Dokumentation hervorheben:

Details zur CPython-Implementierung: In CPython kann aufgrund der globalen Interpretersperre nur ein Thread Python Code auf einmal ausführen (auch wenn bestimmte leistungsorientierte Bibliotheken diese Einschränkung möglicherweise umgehen) Um die Rechenressourcen von Multi-Core-Rechnern besser nutzen zu können, sollten Sie multiprocessing oder concurrent.futures.ProcessPoolExecutor verwenden. Wenn Sie jedoch mehrere E/A-Operationen ausführen möchten, ist Threading immer noch ein geeignetes Modell. O-gebundene Aufgaben gleichzeitig.

Dieser Link verweist auf den Glossareintrag für global interpreter lock , der erklärt, dass die GIL impliziert, dass Thread-Parallelität in Python nicht geeignet ist für CPU-gebundene Tasks) :

Der vom CPython-Interpreter verwendete Mechanismus, um sicherzustellen, dass immer nur ein Thread ausgeführt wird Python Bytecode. Dies vereinfacht die CPython-Implementierung, indem das Objektmodell erstellt wird (einschließlich kritischer integrierter Typen wie dict). Implizit sicher gegen gleichzeitigen Zugriff: Das Sperren des gesamten Interpreters erleichtert das Multithreading des Interpreters auf Kosten eines Großteils der Parallelität, die Multiprozessor-Maschinen bieten.

Einige Erweiterungsmodule, entweder Standardmodule oder solche von Drittanbietern, sind jedoch so konzipiert, dass sie die GIL freigeben, wenn rechenintensive Aufgaben wie Komprimierung oder Hashing ausgeführt werden. Außerdem wird die GIL bei E/A-Vorgängen immer freigegeben.

Frühere Versuche, einen "Free-Threaded" -Interpreter (der gemeinsam genutzte Daten mit einer viel feineren Granularität sperrt) zu erstellen, waren nicht erfolgreich, da die Leistung im allgemeinen Fall mit einem einzelnen Prozessor gelitten hat. Es wird angenommen, dass die Überwindung dieses Leistungsproblems die Implementierung wesentlich komplizierter und daher kostenintensiver in der Wartung machen würde.

Dieses Zitat impliziert auch, dass Diktate und damit die Variablenzuweisung auch als CPython-Implementierungsdetail threadsicher sind:

Als nächstes erklären die docs für das multiprocessing-Paket , wie es die GIL durch den Laichprozess überwindet, während es eine Schnittstelle ähnlich der von threading verfügbar macht:

multiprocessing ist ein Paket, das das Starten von Prozessen mithilfe einer API unterstützt, die dem Threading-Modul ähnelt. Das Multiprocessing-Paket bietet sowohl lokale als auch Remote-Parallelität, wodurch die globale Interpretersperre effektiv umgangen wird, indem Unterprozesse anstelle von Threads verwendet werden. Aus diesem Grund kann der Programmierer mit dem Multiprozessormodul mehrere Prozessoren auf einer bestimmten Maschine voll ausnutzen. Es läuft sowohl unter Unix als auch unter Windows.

Und das docs for concurrent.futures.ProcessPoolExecutor erklärt, dass es multiprocessing als Backend verwendet:

Die ProcessPoolExecutor-Klasse ist eine Executor-Unterklasse, die einen Pool von Prozessen verwendet, um Aufrufe asynchron auszuführen. ProcessPoolExecutor verwendet das Multiprocessing-Modul, mit dem die globale Interpretersperre umgangen werden kann. Es bedeutet jedoch auch, dass nur auswählbare Objekte ausgeführt und zurückgegeben werden können.

dies sollte im Gegensatz zu der anderen Basisklasse ThreadPoolExecutor stehen, die verwendet Threads anstelle von Prozessen

ThreadPoolExecutor ist eine Executor-Unterklasse, die einen Pool von Threads verwendet, um Aufrufe asynchron auszuführen.

daraus schließen wir, dass ThreadPoolExecutor nur für E/A-gebundene Tasks geeignet ist, während ProcessPoolExecutor auch CPU-gebundene Tasks verarbeiten kann.

Die folgende Frage fragt, warum die GIL überhaupt existiert: Warum die globale Interpretersperre?

Prozess-gegen-Thread-Experimente

Bei Multiprocessing vs Threading Python habe ich eine experimentelle Analyse von Prozess vs Threads in Python durchgeführt.

Schnelle Vorschau der Ergebnisse:

enter image description here

Ich möchte ein Beispiel aus dem Buch Multithreading für Visual Effects veröffentlichen. Hier ist also eine klassische Deadlock-Situation

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

Betrachten Sie nun die Ereignisse in der Folge, die zu einem Deadlock führen.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
║   ║ Main Thread                            ║ Other Thread                         ║
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
║ 1 ║ Python Command acquires GIL            ║ Work started                         ║
║ 2 ║ Computation requested                  ║ MyCallback runs and acquires MyMutex ║
║ 3 ║                                        ║ MyCallback now waits for GIL         ║
║ 4 ║ MyCallback runs and waits for MyMutex  ║ waiting for GIL                      ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
0
user1767754

Warum Python (CPython und andere) verwendet die GIL

Von http://wiki.python.org/moin/GlobalInterpreterLock

In CPython ist die globale Interpretersperre (GIL) ein Mutex, der verhindert, dass mehrere native Threads Python Bytecodes gleichzeitig ausführen. Diese Sperre ist hauptsächlich erforderlich, weil die Speicherverwaltung von CPython nicht threadsicher ist.

Wie entferne ich es aus Python?

Wie Lua könnte Python mehrere VMs starten, aber python tut das nicht, ich denke, es sollte noch andere Gründe geben.

In Numpy oder einer anderen python erweiterten Bibliothek kann die Freigabe der GIL für andere Threads manchmal die Effizienz des gesamten Programms steigern.

0
maoyang