In einer Funktion:
a += 1
wird vom Compiler als interpretiert assign to a => Create local variable a
, was nicht das ist, was Sie wollen. Es wird wahrscheinlich mit einem a not initialized
Fehler fehlschlagen , da das (lokale) a tatsächlich nicht initialisiert wurde:
>>> a = 1
>>> def f():
... a += 1
...
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment
Sie könnten das bekommen, was Sie wollen, mit dem global
Schlüsselwort (sehr verpönt und aus guten Gründen) , wie folgt:
>>> def f():
... global a
... a += 1
...
>>> a
1
>>> f()
>>> a
2
Im Allgemeinen sollten Sie jedoch die Verwendung globaler Variablen vermeiden , die extrem schnell außer Kontrolle geraten. Dies gilt insbesondere für Multithread-Programme, bei denen Sie keinen Synchronisationsmechanismus haben, um thread1
zu wissen, wann a
Änderungen vorgenommen wurden. Kurz gesagt: Threads sind kompliziert , und Sie können kein intuitives Verständnis der Reihenfolge erwarten, in der Ereignisse auftreten, wenn zwei (oder mehr) Threads mit demselben Wert arbeiten. Die Sprache, der Compiler, das Betriebssystem, der Prozessor ... können ALLE eine Rolle spielen und entscheiden, die Reihenfolge der Operationen aus Gründen der Geschwindigkeit, der Praktikabilität oder aus anderen Gründen zu ändern.
Der richtige Weg für diese Art von Dingen besteht darin, Python-Freigabetools ( Sperren
und Freunde) zu verwenden oder Daten über eine Warteschlange zu kommunizieren, anstatt sie freizugeben, z. B.:
from threading import Thread
from queue import Queue
import time
def thread1(threadname, q):
while True:
a = q.get()
if a is None: return
print a
def thread2(threadname, q):
a = 0
for _ in xrange(10):
a += 1
q.put(a)
time.sleep(1)
q.put(None)
queue = Queue()
thread1 = Thread( target=thread1, args=("Thread-1", queue) )
thread2 = Thread( target=thread2, args=("Thread-2", queue) )
thread1.start()
thread2.start()
thread1.join()
thread2.join()
a
. Es ist das Standardblockierungsverhalten der Warteschlange, das die Synchronisierung erstellt. Die Anweisunga = q.get()
blockiert (wartet), bis ein Wert a verfügbar ist. Die Variableq
ist lokal: Wenn Sie ihr einen anderen Wert zuweisen, geschieht dies nur lokal. Die im Code zugewiesene Warteschlange ist jedoch die im Hauptthread definierte.Ein Schloss sollte in Betracht gezogen werden, wie z
threading.Lock
. Siehe Lock-Objekte für weitere Informationen.Die akzeptierte Antwort KANN 10 von thread1 drucken, was nicht das ist, was Sie wollen. Sie können den folgenden Code ausführen, um den Fehler leichter zu verstehen.
def thread1(threadname): while True: if a % 2 and not a % 2: print "unreachable." def thread2(threadname): global a while True: a += 1
Die Verwendung eines Schlosses kann das Ändern beim
a
mehrmaligen Lesen verbieten :def thread1(threadname): while True: lock_a.acquire() if a % 2 and not a % 2: print "unreachable." lock_a.release() def thread2(threadname): global a while True: lock_a.acquire() a += 1 lock_a.release()
Wenn ein Thread die Variable für längere Zeit verwendet, ist es eine gute Wahl, sie zuerst in eine lokale Variable zu kopieren.
quelle
Vielen Dank Jason Pan für den Vorschlag dieser Methode. Die if-Anweisung thread1 ist nicht atomar, sodass Thread2 während der Ausführung dieser Anweisung in Thread1 eindringen kann und nicht erreichbarer Code erreicht werden kann. Ich habe Ideen aus den vorherigen Beiträgen in einem vollständigen Demonstrationsprogramm (unten) organisiert, das ich mit Python 2.7 ausgeführt habe.
Ich bin mir sicher, dass wir mit einigen nachdenklichen Analysen weitere Erkenntnisse gewinnen können, aber im Moment denke ich, dass es wichtig ist zu demonstrieren, was passiert, wenn nicht-atomares Verhalten auf Threading trifft.
# ThreadTest01.py - Demonstrates that if non-atomic actions on # global variables are protected, task can intrude on each other. from threading import Thread import time # global variable a = 0; NN = 100 def thread1(threadname): while True: if a % 2 and not a % 2: print("unreachable.") # end of thread1 def thread2(threadname): global a for _ in range(NN): a += 1 time.sleep(0.1) # end of thread2 thread1 = Thread(target=thread1, args=("Thread1",)) thread2 = Thread(target=thread2, args=("Thread2",)) thread1.start() thread2.start() thread2.join() # end of ThreadTest01.py
Wie vorhergesagt, wird beim Ausführen des Beispiels manchmal der "nicht erreichbare" Code tatsächlich erreicht, wodurch eine Ausgabe erzeugt wird.
Als ich ein Thread zum Erfassen / Freigeben von Sperren in Thread1 einfügte, stellte ich fest, dass die Wahrscheinlichkeit, dass die Nachricht "nicht erreichbar" gedruckt wird, stark verringert war. Um die Nachricht zu sehen, reduzierte ich die Schlafzeit auf 0,01 Sekunden und erhöhte NN auf 1000.
Mit einem Lock Acquise / Release-Paar in Thread1 habe ich nicht erwartet, dass die Nachricht überhaupt angezeigt wird, aber sie ist da. Nachdem ich ein Lock Acquise / Release-Paar auch in Thread2 eingefügt habe, wurde die Meldung nicht mehr angezeigt. Im Nachhinein ist die Inkrement-Anweisung in Thread2 wahrscheinlich auch nicht atomar.
quelle
Nun, laufendes Beispiel:
WARNUNG! TUN SIE DAS NIEMALS ZU HAUSE / ARBEITEN! Nur im Klassenzimmer;)
Verwenden Sie Semaphoren, gemeinsam genutzte Variablen usw., um Eilbedingungen zu vermeiden.
from threading import Thread import time a = 0 # global variable def thread1(threadname): global a for k in range(100): print("{} {}".format(threadname, a)) time.sleep(0.1) if k == 5: a += 100 def thread2(threadname): global a for k in range(10): a += 1 time.sleep(0.2) thread1 = Thread(target=thread1, args=("Thread-1",)) thread2 = Thread(target=thread2, args=("Thread-2",)) thread1.start() thread2.start() thread1.join() thread2.join()
und die Ausgabe:
Thread-1 0 Thread-1 1 Thread-1 2 Thread-1 2 Thread-1 3 Thread-1 3 Thread-1 104 Thread-1 104 Thread-1 105 Thread-1 105 Thread-1 106 Thread-1 106 Thread-1 107 Thread-1 107 Thread-1 108 Thread-1 108 Thread-1 109 Thread-1 109 Thread-1 110 Thread-1 110 Thread-1 110 Thread-1 110 Thread-1 110 Thread-1 110 Thread-1 110 Thread-1 110
Wenn das Timing richtig wäre, würde die
a += 100
Operation übersprungen:Der Prozessor wird bei T ausgeführt
a+100
und erhält 104. Aber er stoppt und springt zum nächsten Thread. Hier wird bei T + 1a+1
mit dem alten Wert von a ,a == 4
. Es berechnet also 5. Springe zurück (bei T + 2), Thread 1 und schreibea=104
in den Speicher. Zurück bei Thread 2 ist die Zeit T + 3 unda=5
in den Speicher schreiben . Voila! Die nächste Druckanweisung gibt 5 anstelle von 104 aus.SEHR böser Fehler, der reproduziert und abgefangen werden muss.
quelle