Python Language
Umgang mit der Global Interpreter Lock (GIL)
Suche…
Bemerkungen
Warum gibt es eine GIL?
Die GIL gibt es seit der Gründung von Python-Threads im Jahr 1992 in CPython. Sie soll Thread-Sicherheit beim Ausführen von Python-Code gewährleisten. Mit einer GIL geschriebene Python-Interpreter verhindern, dass mehrere native Threads Python-Bytecodes gleichzeitig ausführen. Dies macht es Plugins leicht, sicherzustellen, dass ihr Code Thread-sicher ist: Sperren Sie einfach die GIL, und nur Ihr aktiver Thread kann ausgeführt werden, sodass Ihr Code automatisch Thread-sicher ist.Kurze Version: Die GIL stellt sicher, dass unabhängig von der Anzahl der Prozessoren und Threads nur ein Thread eines Python-Interpreters gleichzeitig ausgeführt wird.
Dies hat viele Vorteile bei der Benutzerfreundlichkeit, aber auch viele negative Vorteile.
Beachten Sie, dass eine GIL keine Anforderung der Python-Sprache ist. Daher können Sie nicht direkt vom Standard-Python-Code auf die GIL zugreifen. Nicht alle Implementierungen von Python verwenden eine GIL.
Dolmetscher, die eine GIL haben: CPython, PyPy, Cython (aber Sie können die GIL mit nogil
deaktivieren)
Interpreter ohne GIL: Jython, IronPython
Details zur Funktionsweise der GIL:
Wenn ein Thread läuft, sperrt er die GIL. Wenn ein Thread ausgeführt werden soll, fordert er die GIL an und wartet, bis sie verfügbar ist. In CPython, vor Version 3.2, prüfte der laufende Thread nach einer bestimmten Anzahl von Python-Anweisungen, ob anderer Code die Sperre wünschte (d. H. Er gab die Sperre auf und forderte sie erneut auf). Diese Methode neigte dazu, einen Thread-Hunger zu verursachen, hauptsächlich, weil der Thread, der die Sperre aufgehoben hatte, ihn erneut erwerben würde, bevor die wartenden Threads eine Chance hatten, aufzuwachen. Seit 3.2 warten Threads, die möchten, dass die GIL für einige Zeit auf die Sperre wartet, und setzen danach eine gemeinsam genutzte Variable, die den laufenden Thread zwingt, nachzugeben. Dies kann jedoch zu drastisch längeren Ausführungszeiten führen. Weitere Informationen finden Sie unter den Links von dabeaz.com (im Abschnitt mit den Referenzen).CPython gibt die GIL automatisch frei, wenn ein Thread eine E / A-Operation ausführt. Bildbearbeitungsbibliotheken und numerische Anzahlprozeduren geben die GIL vor der Verarbeitung frei.
Vorteile der GIL
Für Dolmetscher, die die GIL verwenden, ist die GIL systemisch. Es wird verwendet, um den Status der Anwendung zu erhalten. Vorteile umfassen:- Garbage Collection - Thread-sichere Referenzzählungen müssen geändert werden, während die GIL gesperrt ist. In CPython ist die gesamte Garbarge-Sammlung an die GIL gebunden. Dies ist eine große Sache. In dem Wiki-Artikel zu Python.org über die GIL (aufgeführt in Referenzen unten) finden Sie Details dazu, was noch funktionieren muss, wenn Sie die GIL entfernen möchten.
- Einfach für Programmierer, die sich mit der GIL befassen - alles zu sperren ist einfach, aber leicht zu programmieren
- Erleichtert den Import von Modulen aus anderen Sprachen
Folgen der GIL
Die GIL erlaubt es nur einem Thread, jeweils Python-Code innerhalb des Python-Interpreters auszuführen. Das bedeutet, dass das Multithreading von Prozessen, die strengen Python-Code ausführen, einfach nicht funktioniert. Wenn Sie Threads gegen die GIL verwenden, haben Sie mit den Threads wahrscheinlich eine schlechtere Leistung als bei einem einzelnen Thread.Verweise:
https://wiki.python.org/moin/GlobalInterpreterLock - eine kurze Zusammenfassung der Funktionsweise sowie genaue Angaben zu allen Vorteilen
http://programmers.stackexchange.com/questions/186889/why-was-python-wrched-with-the-gil - übersichtlich geschriebene Zusammenfassung
http://www.dabeaz.com/python/UnderstandingGIL.pdf - wie die GIL funktioniert und warum sie auf mehreren Kernen langsamer wird
http://www.dabeaz.com/GIL/gilvis/index.html - Visualisierung der Daten, die zeigen, wie die GIL Threads blockiert
http://jeffknupp.com/blog/2012/03/31/pythons-hardest-problem/ - Geschichte des GIL-Problems einfach zu verstehen
https://jeffknupp.com/blog/2013/06/30/pythons-hardest-problem-revisited/ - Details zur Lösung der Einschränkungen der GIL
Multiprocessing.Pool
Die einfache Antwort auf die Frage, wie Threads in Python verwendet werden sollen, lautet: "Nicht. Verwenden Sie stattdessen Prozesse." Mit dem Multiprocessing-Modul können Sie Prozesse mit einer ähnlichen Syntax wie das Erstellen von Threads erstellen, aber ich bevorzuge die Verwendung ihres praktischen Pool-Objekts.
Mit dem Code, mit dem David Beazley zuerst die Gefahren von Threads gegen die GIL aufgezeigt hat , schreiben wir ihn mit Multiprocessing neu. Pool :
David Beazleys Code, der Probleme beim Einfädeln von GIL zeigte
from threading import Thread
import time
def countdown(n):
while n > 0:
n -= 1
COUNT = 10000000
t1 = Thread(target=countdown,args=(COUNT/2,))
t2 = Thread(target=countdown,args=(COUNT/2,))
start = time.time()
t1.start();t2.start()
t1.join();t2.join()
end = time.time()
print end-start
Mit multiprocessing.Pool neu geschrieben: import multiprocessing
import time
def countdown(n):
while n > 0:
n -= 1
COUNT = 10000000
start = time.time()
with multiprocessing.Pool as pool:
pool.map(countdown, [COUNT/2, COUNT/2])
pool.close()
pool.join()
end = time.time()
print(end-start)
Anstatt Threads zu erstellen, werden neue Prozesse erstellt. Da jeder Prozess ein eigener Interpreter ist, gibt es keine GIL-Kollisionen. multiprocessing.Pool öffnet so viele Prozesse, wie Kerne auf der Maschine vorhanden sind, im obigen Beispiel wären jedoch nur zwei erforderlich. In einem realen Szenario möchten Sie, dass Ihre Liste mindestens so lang ist, wie Prozessoren auf Ihrem Computer vorhanden sind. Der Pool führt die von Ihnen angegebene Funktion mit jedem Argument aus, bis zu der Anzahl der erstellten Prozesse. Wenn die Funktion abgeschlossen ist, werden alle verbleibenden Funktionen in der Liste für diesen Prozess ausgeführt.
Ich habe festgestellt, dass die Prozesse auch dann noch vorhanden sind, wenn Sie die with
Anweisung verwenden. Um Ressourcen zu bereinigen, schließe ich immer meine Pools.
Cython Nogil:
Cython ist ein alternativer Python-Interpreter. Es verwendet die GIL, kann jedoch deaktiviert werden. Sehen Sie ihre Dokumentation
Als Beispiel verwenden wir den Code , mit dem David Beazley zuerst die Gefahren von Threads gegen die GIL aufzeigte, mit Nogil neu schreiben:
David Beazleys Code, der Probleme beim Einfädeln von GIL zeigte
from threading import Thread
import time
def countdown(n):
while n > 0:
n -= 1
COUNT = 10000000
t1 = Thread(target=countdown,args=(COUNT/2,))
t2 = Thread(target=countdown,args=(COUNT/2,))
start = time.time()
t1.start();t2.start()
t1.join();t2.join()
end = time.time()
print end-start
Mit Nogil neu geschrieben (NUR FUNKTIONIERT IN CYTHON):
from threading import Thread
import time
def countdown(n):
while n > 0:
n -= 1
COUNT = 10000000
with nogil:
t1 = Thread(target=countdown,args=(COUNT/2,))
t2 = Thread(target=countdown,args=(COUNT/2,))
start = time.time()
t1.start();t2.start()
t1.join();t2.join()
end = time.time()
print end-start
So einfach ist das, solange Sie Cython verwenden. Beachten Sie, dass Sie in der Dokumentation darauf hingewiesen werden, dass Sie keine Python-Objekte ändern müssen:
Code im Rumpf der Anweisung darf Python-Objekte auf keine Weise manipulieren und darf nichts aufrufen, das Python-Objekte manipuliert, ohne die GIL erneut zu erwerben. Cython prüft dies derzeit nicht.