Wenn Sie sich auf eine Implementierung von Python verlassen, die über eine globale Interpreter-Sperre (dh CPython) verfügt und Multithread-Code schreibt, benötigen Sie überhaupt Sperren?
Wenn die GIL nicht zulässt, dass mehrere Anweisungen gleichzeitig ausgeführt werden, sind gemeinsame Daten dann nicht zum Schutz unnötig?
Es tut mir leid, wenn dies eine dumme Frage ist, aber ich habe mich immer über Python auf Multiprozessor- / Core-Computern gewundert.
Das Gleiche gilt für jede andere Sprachimplementierung mit einer GIL.
python
multithreading
locking
Corey Goldberg
quelle
quelle
Antworten:
Sie benötigen weiterhin Sperren, wenn Sie den Status zwischen Threads teilen. Die GIL schützt den Dolmetscher nur intern. Sie können immer noch inkonsistente Aktualisierungen in Ihrem eigenen Code haben.
Zum Beispiel:
#!/usr/bin/env python import threading shared_balance = 0 class Deposit(threading.Thread): def run(self): for _ in xrange(1000000): global shared_balance balance = shared_balance balance += 100 shared_balance = balance class Withdraw(threading.Thread): def run(self): for _ in xrange(1000000): global shared_balance balance = shared_balance balance -= 100 shared_balance = balance threads = [Deposit(), Withdraw()] for thread in threads: thread.start() for thread in threads: thread.join() print shared_balance
Hier kann Ihr Code zwischen dem Lesen des freigegebenen Status (
balance = shared_balance
) und dem Zurückschreiben des geänderten Ergebnisses (shared_balance = balance
) unterbrochen werden , was zu einem verlorenen Update führt. Das Ergebnis ist ein zufälliger Wert für den gemeinsam genutzten Status.Um die Aktualisierungen konsistent zu machen, müssten Ausführungsmethoden den freigegebenen Status um die Lese-, Änderungs- und Schreibabschnitte (innerhalb der Schleifen) sperren oder auf irgendeine Weise erkennen, wann sich der freigegebene Status seit dem Lesen geändert hat .
quelle
shared_balance += 100
undshared_balance -= 100
?Nein - die GIL schützt Python-Interna nur vor mehreren Threads, die ihren Status ändern. Dies ist eine sehr niedrige Sperrstufe, die nur ausreicht, um die eigenen Strukturen von Python in einem konsistenten Zustand zu halten. Es behandelt nicht die Sperre auf Anwendungsebene , die Sie durchführen müssen, um die Thread-Sicherheit in Ihrem eigenen Code zu behandeln.
Das Wesentliche beim Sperren besteht darin, sicherzustellen, dass ein bestimmter Codeblock nur von einem Thread ausgeführt wird. Die GIL erzwingt dies für Blöcke mit der Größe eines einzelnen Bytecodes. Normalerweise soll die Sperre jedoch einen größeren Codeblock umfassen.
quelle
Zur Diskussion hinzufügen:
Da die GIL vorhanden ist, sind einige Operationen in Python atomar und benötigen keine Sperre.
http://www.python.org/doc/faq/library/#what-kinds-of-global-value-mutation-are-thread-safe
Wie in den anderen Antworten angegeben, müssen Sie jedoch immer noch Sperren verwenden, wenn die Anwendungslogik dies erfordert (z. B. bei einem Producer / Consumer-Problem).
quelle
Dieser Beitrag beschreibt die GIL auf einem ziemlich hohen Niveau:
Von besonderem Interesse sind diese Zitate:
und
Es hört sich so an, als ob die GIL nur weniger mögliche Instanzen für einen Kontextwechsel bereitstellt und Multi-Core- / Prozessorsysteme in Bezug auf jede Python-Interpreter-Instanz als einen einzigen Kern verhalten. Ja, Sie müssen also immer noch Synchronisationsmechanismen verwenden.
quelle
sys.getcheckinterval()
Gibt an, wie viele Bytecode-Anweisungen zwischen "GIL-Releases" ausgeführt werden (und seit mindestens 2,5 sind es 100 (nicht 10)). In 3.2 wird möglicherweise auf ein zeitbasiertes Intervall (ca. 5 ms) umgeschaltet, anstatt auf Befehlszählungen. Die Änderung kann auch auf 2.7 angewendet werden, obwohl noch in Arbeit ist.Die globale Interpreter-Sperre verhindert, dass Threads gleichzeitig auf den Interpreter zugreifen (daher verwendet CPython immer nur einen Kern). Soweit ich weiß, werden die Threads jedoch immer noch unterbrochen und präventiv geplant. Dies bedeutet, dass Sie weiterhin Sperren für gemeinsam genutzte Datenstrukturen benötigen, damit Ihre Threads nicht gegenseitig auf die Zehen treten.
Die Antwort, auf die ich immer wieder gestoßen bin, ist, dass Multithreading in Python aus diesem Grund den Aufwand selten wert ist. Ich habe gute Dinge über das PyProcessing- Projekt gehört, das das Ausführen mehrerer Prozesse so einfach wie Multithreading mit gemeinsam genutzten Datenstrukturen, Warteschlangen usw. macht. (PyProcessing wird als Multiprocessing- Modul in die Standardbibliothek des kommenden Python 2.6 eingeführt .) Dies führt Sie um die GIL herum, da jeder Prozess seinen eigenen Interpreter hat.
quelle
Denk darüber so:
Auf einem Computer mit einem Prozessor erfolgt Multithreading, indem ein Thread angehalten und ein anderer schnell genug gestartet wird, damit er zur gleichen Zeit ausgeführt wird. Dies ist wie Python mit der GIL: Es wird immer nur ein Thread ausgeführt.
Das Problem ist, dass der Thread überall angehalten werden kann, wenn ich beispielsweise b = (a + b) * 3 berechnen möchte, kann dies zu folgenden Anweisungen führen:
1 a += b 2 a *= 3 3 b = a
Nehmen wir nun an, dass der Thread in einem Thread ausgeführt wird und dieser Thread nach Zeile 1 oder 2 angehalten wird und dann ein anderer Thread aktiviert wird:
b = 5
Wenn der andere Thread fortgesetzt wird, wird b durch die alten berechneten Werte überschrieben, was wahrscheinlich nicht den Erwartungen entspricht.
Sie können also sehen, dass Sie, obwohl sie nicht WIRKLICH gleichzeitig ausgeführt werden, dennoch gesperrt werden müssen.
quelle
Sie müssen weiterhin Sperren verwenden (Ihr Code kann jederzeit unterbrochen werden, um einen anderen Thread auszuführen, und dies kann zu Dateninkonsistenzen führen). Das Problem mit GIL besteht darin, dass verhindert wird, dass Python-Code mehr Kerne gleichzeitig verwendet (oder mehrere Prozessoren, falls verfügbar).
quelle
Schlösser werden noch benötigt. Ich werde versuchen zu erklären, warum sie gebraucht werden.
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 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 Threads, die Interpreter Halten läuft zu jedem Zeitpunkt der Zeit.
Angenommen, es gibt zwei Threads, z. B. t1 und t2, und beide möchten zwei Anweisungen ausführen, die den Wert einer globalen Variablen lesen und inkrementieren.
#increment value global var read_var = var var = read_var + 1
Wie oben beschrieben, stellt GIL nur sicher, dass zwei Threads einen Befehl nicht gleichzeitig ausführen können, was bedeutet, dass beide Threads
read_var = var
zu einem bestimmten Zeitpunkt nicht ausgeführt werden können. Aber sie können Anweisungen nacheinander ausführen, und Sie können immer noch Probleme haben. Betrachten Sie diese Situation:read_var = var
. Read_var in t1 ist also 0. GIL stellt nur sicher, dass diese Leseoperation zu diesem Zeitpunkt für keinen anderen Thread ausgeführt wird.read_var = var
. Aber read_var ist immer noch 0. Also ist read_var in t2 0.var = read_var+1
und var wird 1.var = read_var+1
und var wird 1.var
2 werden sollte.quelle
Ein kleines Update aus Will Harris 'Beispiel:
class Withdraw(threading.Thread): def run(self): for _ in xrange(1000000): global shared_balance if shared_balance >= 100: balance = shared_balance balance -= 100 shared_balance = balance
Geben Sie eine Wertprüfungserklärung in die Auszahlung ein und ich sehe kein Negativ mehr und die Aktualisierungen scheinen konsistent zu sein. Meine Frage ist:
Wenn GIL verhindert, dass zu einem atomaren Zeitpunkt nur ein Thread ausgeführt werden kann, wo wäre dann der veraltete Wert? Wenn kein veralteter Wert, warum brauchen wir eine Sperre? (Angenommen, wir sprechen nur über reinen Python-Code)
Wenn ich das richtig verstehe, würde die obige Bedingungsprüfung in einer echten Threading-Umgebung nicht funktionieren . Wenn mehr als ein Thread gleichzeitig ausgeführt wird, kann ein veralteter Wert erstellt werden, daher die Inkonsistenz des Freigabestatus. Dann benötigen Sie wirklich eine Sperre. Aber wenn Python wirklich immer nur einen Thread zulässt (Time Slicing Threading), sollte es nicht möglich sein, dass veralteter Wert existiert, oder?
quelle