it-swarm.com.de

Verwenden einer globalen Variablen mit einem Thread

Wie teile ich eine globale Variable mit Thread?

Mein Python Codebeispiel ist:

from threading import Thread
import time
a = 0  #global variable

def thread1(threadname):
    #read variable "a" modify by thread 2

def thread2(threadname):
    while 1:
        a += 1
        time.sleep(1)

thread1 = Thread( target=thread1, args=("Thread-1", ) )
thread2 = Thread( target=thread2, args=("Thread-2", ) )

thread1.join()
thread2.join()

Ich weiß nicht, wie ich die beiden Threads dazu bringen soll, eine Variable gemeinsam zu nutzen.

67
Mauro Midolo

Sie müssen nur a in thread2 Als global deklarieren, damit Sie kein a ändern, das für diese Funktion lokal ist.

def thread2(threadname):
    global a
    while True:
        a += 1
        time.sleep(1)

In thread1 Müssen Sie nichts Besonderes tun, solange Sie nicht versuchen, den Wert von a zu ändern (wodurch eine lokale Variable erzeugt wird, die die globale abschattet; Verwenden Sie global a, wenn Sie müssen)>

def thread1(threadname):
    #global a       # Optional if you treat a as read-only
    while a < 10:
        print a
76
chepner

In einer Funktion:

a += 1

wird vom Compiler als assign to a => Create local variable a interpretiert, was nicht das ist, was Sie wollen. Es wird wahrscheinlich mit einem a not initialized Fehler scheitern, 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

Möglicherweise erhalten Sie mit dem (aus guten Gründen sehr verpönten) Schlüsselwort global das, was Sie möchten:

>>> def f():
...     global a
...     a += 1
... 
>>> a
1
>>> f()
>>> a
2

Im Allgemeinen sollten Sie jedoch vermeiden globale Variablen verwenden, die extrem schnell außer Kontrolle geraten. Dies gilt insbesondere für Multithread-Programme, bei denen Sie keinen Synchronisationsmechanismus für Ihren thread1 Haben, um zu wissen, wann a geändert wurde. Kurz gesagt: Threads sind kompliziert , und Sie können nicht erwarten, dass Sie die Reihenfolge, in der Ereignisse auftreten, wenn zwei (oder mehr) Threads funktionieren, intuitiv verstehen auf den gleichen Wert. 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 einem anderen Grund zu ändern.

Der richtige Weg für diese Art von Dingen ist die Verwendung von Python= Freigabe-Tools ( Sperren und Freunden), oder besser Daten über eine Warteschlange zu kommunizieren = anstatt es zu teilen, zB so:

from threading import Thread
from queue import Queue
import time

def thread1(threadname, q):
    #read variable "a" modify by thread 2
    while True:
        a = q.get()
        if a is None: return # Poison pill
        print a

def thread2(threadname, q):
    a = 0
    for _ in xrange(10):
        a += 1
        q.put(a)
        time.sleep(1)
    q.put(None) # Poison pill

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()
44
val

Es sollte in Betracht gezogen werden, eine Sperre zu verwenden, z. B. threading.Lock. Siehe Sperrobjekte für weitere Informationen.

Die akzeptierte Antwort KANN 10 durch 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 einer Sperre kann das Ändern von a während des Lesens mehrerer Male 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 der Thread die Variable für längere Zeit verwendet, ist es eine gute Wahl, sie zuerst in eine lokale Variable zu kopieren.

4
Jason Pan

Vielen Dank Jason Pan, der diese Methode vorgeschlagen hat. Die Anweisung thread1 if ist nicht atomar. Während diese Anweisung ausgeführt wird, kann thread2 in thread1 eindringen und nicht erreichbaren Code erreichen. Ich habe die Ideen aus den vorherigen Beiträgen in einem vollständigen Demonstrationsprogramm (siehe unten) zusammengefasst, das ich mit Python 2.7) ausgeführt habe.

Ich bin mir sicher, dass wir mit einer sorgfältigen Analyse weitere Einsichten gewinnen können, aber im Moment ist es wichtig 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, was zu einer Ausgabe führt.

Als ich ein Lock Acquisition/Release-Paar in Thread1 einfügte, stellte ich fest, dass die Wahrscheinlichkeit, dass die Nachricht "nicht erreichbar" gedruckt wird, stark reduziert war. Um die Meldung zu sehen, habe ich die Ruhezeit auf 0,01 Sekunden verringert und die NN auf 1000 erhöht.

Mit einem Lock Acquire/Release-Paar in Thread1 habe ich nicht erwartet, dass die Nachricht überhaupt angezeigt wird, aber sie ist da. Nachdem ich auch in Thread2 ein Paar zum Erfassen/Freigeben einer Sperre eingefügt hatte, wurde die Meldung nicht mehr angezeigt. Im Nachhinein ist die Inkrement-Anweisung in thread2 wahrscheinlich auch nicht atomar.

3
Krista M Hill

Nun, laufendes Beispiel:

ACHTUNG! TUN SIE NIEMALS AT HOME/WORK! Nur im Klassenzimmer;)

Verwenden Sie Semaphoren, gemeinsam genutzte Variablen usw., um Rush-Bedingungen 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 stimmt, wird die Operation a += 100 Übersprungen:

Der Prozessor wird bei T a+100 Ausgeführt und erhält 104. Er stoppt jedoch und springt zum nächsten Thread. Bei T + 1 wird a+1 Mit dem alten Wert von a a == 4 Ausgeführt. Also berechnet es 5. Springe zurück (bei T + 2), Thread 1 und schreibe a=104 In den Speicher. Nun zurück zu Thread 2, Zeit ist T + 3 und schreibe a=5 In den Speicher. Voila! Die nächste Druckanweisung gibt 5 anstelle von 104 aus.

Sehr böser Bug, der reproduziert und gefangen werden kann.

1
visoft