Was ist die globale Interpretersperre (GIL) in CPython?


244

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

Beim Entfernen der GIL aus Python wurde viel Lärm gemacht, und ich würde gerne verstehen, warum das so wichtig ist. Ich habe selbst noch nie einen Compiler oder einen Interpreter geschrieben. Seien Sie also nicht sparsam mit Details, ich werde sie wahrscheinlich brauchen, um sie zu verstehen.


3
Sehen Sie, wie David Beazley Ihnen alles erzählt, was Sie schon immer über die GIL wissen wollten.
Hughdbrown

1
Hier ist ein längerer Artikel über die GIL und das Threading in Python, den ich vor einiger Zeit geschrieben habe. Es geht ziemlich
jnoller

Hier ist ein Code, der die Auswirkungen von GIL demonstriert: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu

3
Ich finde, das ist die beste Erklärung für GIL. Bitte lesen Sie. dabeaz.com/python/UnderstandingGIL.pdf
suhao399

realpython.com/python-gil Ich fand das nützlich
qwr

Antworten:


220

Pythons GIL soll den Zugriff auf Interpreter-Interna aus verschiedenen Threads serialisieren. Auf Mehrkernsystemen bedeutet dies, dass mehrere Threads mehrere Kerne nicht effektiv nutzen können. (Wenn die GIL nicht zu diesem Problem geführt hätte, würden sich die meisten Menschen nicht für die GIL interessieren - sie wird nur aufgrund der zunehmenden Verbreitung von Mehrkernsystemen als Problem angesprochen.) Wenn Sie sie im Detail verstehen möchten, Sie können dieses Video ansehen oder sich diese Folien ansehen . Es könnten zu viele Informationen sein, aber dann haben Sie nach Details gefragt :-)

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


46
Gute Antwort - im Grunde bedeutet dies, dass Threads in Python nur zum Blockieren von E / A geeignet sind. Ihre App wird niemals über 1 CPU-Kern der Prozessorauslastung hinausgehen
Ana Betts

8
"Als Python-Entwickler stoßen Sie im Allgemeinen nur dann auf die GIL, wenn Sie eine C-Erweiterung schreiben." - Sie wissen möglicherweise nicht, dass die Ursache dafür, dass Ihr Multithread-Code im Schneckentempo ausgeführt wird, die GIL ist, aber Sie ' Ich werde sicherlich seine Auswirkungen spüren. Es überrascht mich immer noch, dass ich 32 Prozesse mit dem gesamten damit verbundenen Overhead benötige, um einen 32-Core-Server mit Python nutzen zu können.
Basic

6
@ PaulBetts: Es ist nicht wahr. Es ist wahrscheinlich , dass die Leistung kritische Code verwendet bereits C - Erweiterungen , die und GIL zB freisetzen können, regex, lxml, numpyModule. Cython erlaubt es, GIL in benutzerdefiniertem Code freizugeben, zBb2a_bin(data)
jfs

5
@Paul Betts: Mit dem Multiprocessing- Modul können Sie mehr als 1 CPU-Code für die Prozessorauslastung erhalten . Das Erstellen mehrerer Prozesse ist "schwerer" als das Erstellen mehrerer Threads. Wenn Sie jedoch wirklich parallel arbeiten müssen, ist dies in Python eine Option.
AJNeufeld

1
@david_adler Ja, das ist immer noch der Fall und wird es wahrscheinlich noch eine Weile bleiben. Das hat Python nicht wirklich davon abgehalten, für viele verschiedene Workloads wirklich nützlich zu sein.
Vinay Sajip

59

Angenommen, Sie haben mehrere Threads, die die Daten des anderen nicht wirklich berühren. Diese sollten so unabhängig wie möglich ausgeführt werden. 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 überhaupt mehrere Threads haben.

Um es in eine reale Analogie zu bringen: Stellen Sie sich 100 Entwickler vor, die in einem Unternehmen mit nur einer einzigen Kaffeetasse arbeiten. Die meisten Entwickler verbrachten ihre Zeit damit, auf Kaffee zu warten, anstatt zu codieren.

Nichts davon ist Python-spezifisch - ich weiß nicht genau, wofür Python überhaupt eine GIL benötigte. Hoffentlich erhalten Sie jedoch eine bessere Vorstellung vom allgemeinen Konzept.


Außer auf die Kaffeetasse zu warten, scheint ein ziemlich E / A-gebundener Prozess zu sein, da sie sicherlich andere Dinge tun können, während sie auf die Tasse warten. Die GIL hat nur sehr geringe Auswirkungen auf schwere E / A-Threads, die ohnehin die meiste Zeit mit Warten verbringen.
Cruncher


36

Lassen Sie uns zunächst verstehen, was die Python-GIL bietet:

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 einzelnen Thread gehalten. Es bedeutet , dass nur der Thread, der den Interpreter hält , wird ausgeführt auf jedem Zeitpunkt .

Warum ist das ein Problem?

Ihre Maschine verfügt möglicherweise über mehrere Kerne / Prozessoren. Mehrere Kerne ermöglichen die gleichzeitige Ausführung mehrerer Threads, dh, mehrere Threads können zu einem bestimmten Zeitpunkt ausgeführt werden. . Da der Interpreter jedoch von einem einzelnen Thread gehalten wird, tun andere Threads nichts, obwohl sie Zugriff auf einen Kern haben. Sie erhalten also keinen Vorteil durch mehrere Kerne, da zu jedem Zeitpunkt nur ein einziger 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.

Potenziell blockierende oder lang laufende Vorgänge wie E / A, Bildverarbeitung und NumPy-Nummernverknüpfung treten jedoch außerhalb der GIL auf. Von hier genommen . Für solche Operationen ist eine Multithread-Operation trotz des Vorhandenseins von GIL immer noch schneller als eine Single-Threaded-Operation. GIL ist also nicht immer ein Engpass.

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


4
Hinweis : PyPy hat die GIL . Referenz : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . Während Ironpython und Jython nicht die GIL haben.
Tasdik Rahman

In der Tat hat PyPy eine GIL, IronPython jedoch nicht.
Emmanuel

@Emmanuel Die Antwort wurde bearbeitet, um PyPy zu entfernen und IronPython einzuschließen.
Akshar Raaj

17

Python erlaubt kein Multithreading im wahrsten Sinne des Wortes. Es verfügt über ein Multithreading-Paket. Wenn Sie jedoch Multithreading-Pakete verwenden möchten, um Ihren Code zu beschleunigen, 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' gleichzeitig ausgeführt werden kann. Ein Thread erwirbt die GIL, erledigt ein wenig Arbeit und leitet die GIL 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 nur mit demselben CPU-Kern ab. All diese GIL-Übergaben erhöhen den Aufwand für die Ausführung. Dies bedeutet, dass die Verwendung des Threading-Pakets häufig keine gute Idee ist, wenn Sie Ihren 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 Effizienz kein Problem darstellt, ist dies völlig in Ordnung und praktisch. Oder wenn Sie Code ausführen, der auf etwas warten muss (wie z. B. eine E / A), kann dies sehr sinnvoll sein. In der Threading-Bibliothek können Sie jedoch keine zusätzlichen CPU-Kerne verwenden.

Multithreading kann an das Betriebssystem ausgelagert werden (durch Multiverarbeitung), an eine externe Anwendung, die Ihren Python-Code aufruft (z. B. Spark oder Hadoop), oder an Code, den Ihr Python-Code aufruft (z. B. Sie könnten Ihren Python haben Code ruft eine C-Funktion auf, die die teuren Multithread-Aufgaben erledigt).


15

Immer wenn zwei Threads auf dieselbe Variable zugreifen, tritt ein Problem auf. 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 Granularität ausgeführt werden, die feiner als eine Python-Anweisung ist. Der laufende Thread erhält eine globale Sperre namens GIL.

Dies bedeutet, wenn Sie mit dem Schreiben von Multithread-Code beginnen, um die Vorteile Ihres Multicore-Prozessors zu nutzen, wird sich Ihre Leistung nicht verbessern. Die übliche Problemumgehung besteht darin, mehrere Prozesse 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 Python nicht eigen, sondern einigen seiner Interpreter, einschließlich des am häufigsten verwendeten CPython. (#edited, siehe Kommentar)

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


Stackless hat noch eine GIL. Stackless verbessert das Threading nicht (wie im Modul) - es bietet eine andere Programmiermethode (Coroutinen), die versucht, das Problem zu umgehen, aber nicht blockierende Funktionen erfordert.
Jnoller

Was ist mit der neuen GIL in 3.2?
new123456

Nur um hinzuzufügen, dass Sie kein Problem haben / Mutexe / Semaphoren benötigen, wenn nur ein Thread den Speicher aktualisiert. @ new123456 Es reduziert die Konflikte und plant Threads besser, ohne die Leistung von Single-Threads zu beeinträchtigen (was an sich beeindruckend ist), aber es ist immer noch eine globale Sperre.
Basic

14

Python 3.7-Dokumentation

Ich möchte auch das folgende Zitat aus der Python- threadingDokumentation hervorheben :

Details zur CPython-Implementierung: In CPython kann aufgrund der globalen Interpreter-Sperre nur ein Thread Python-Code gleichzeitig ausführen (obwohl bestimmte leistungsorientierte Bibliotheken diese Einschränkung möglicherweise überwinden). Wenn Sie möchten, dass Ihre Anwendung die Rechenressourcen von Mehrkernmaschinen besser nutzt, wird empfohlen, multiprocessingoder zu verwenden concurrent.futures.ProcessPoolExecutor. Threading ist jedoch immer noch ein geeignetes Modell, wenn Sie mehrere E / A-gebundene Aufgaben gleichzeitig ausführen möchten.

Dieser Link verweist auf den Glossareintrag, inglobal interpreter lock dem erklärt wird, dass die GIL impliziert, dass Thread-Parallelität in Python für CPU-gebundene Aufgaben ungeeignet ist :

Der Mechanismus, der vom CPython-Interpreter verwendet wird, um sicherzustellen, dass jeweils nur ein Thread Python-Bytecode ausführt. Dies vereinfacht die CPython-Implementierung, indem das Objektmodell (einschließlich kritischer integrierter Typen wie dict) implizit vor gleichzeitigem Zugriff geschützt wird. 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 Module 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 immer freigegeben, wenn E / A ausgeführt wird.

Frühere Bemühungen, einen "Free-Threaded" -Interpreter zu erstellen (der gemeinsam genutzte Daten mit einer viel feineren Granularität sperrt), waren nicht erfolgreich, da die Leistung im Fall eines herkömmlichen Einzelprozessors darunter litt. Es wird angenommen, dass die Überwindung dieses Leistungsproblems die Implementierung viel komplizierter und daher kostspieliger in der Wartung machen würde.

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

Als Nächstes wird in den Dokumenten für das multiprocessingPaket erläutert, wie die GIL durch den Spawning-Prozess überwunden wird, während eine Schnittstelle verfügbar gemacht wird, die der threadingfolgenden ähnelt :

Multiprocessing ist ein Paket, das Spawning-Prozesse mithilfe einer API unterstützt, die dem Threading-Modul ähnelt. Das Multiprocessing-Paket bietet sowohl lokale als auch Remote-Parallelität und umgeht die globale Interpreter-Sperre effektiv, indem Subprozesse anstelle von Threads verwendet werden. Aus diesem Grund ermöglicht das Multiprozessor-Modul dem Programmierer, mehrere Prozessoren auf einer bestimmten Maschine vollständig zu nutzen. Es läuft sowohl unter Unix als auch unter Windows.

Und die Dokumente fürconcurrent.futures.ProcessPoolExecutor erklären, dass es multiprocessingals 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 Interpreter-Sperre umgangen werden kann, aber auch, dass nur auswählbare Objekte ausgeführt und zurückgegeben werden können.

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

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

Daraus schließen wir, dass dies ThreadPoolExecutornur für E / A-gebundene Aufgaben geeignet ist, während ProcessPoolExecutores auch CPU-gebundene Aufgaben 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 Process vs Threads in Python durchgeführt.

Schnelle Vorschau der Ergebnisse:

Geben Sie hier die Bildbeschreibung ein


0

Warum Python (CPython und andere) die GIL verwendet

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 vielleicht mehrere VMs starten, aber Python macht das nicht, ich denke, es sollte noch andere Gründe geben.

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


0

Ich möchte ein Beispiel aus dem Buch Multithreading für visuelle Effekte teilen. 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 Sequenz, 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                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.