Utilizzo di una variabile globale con un thread


86

Come condivido una variabile globale con il thread?

Il mio esempio di codice Python è:

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

Non so come fare in modo che i due thread condividano una variabile.

Risposte:


99

Devi solo dichiarare acome globale in thread2, in modo da non modificare un elemento alocale rispetto a quella funzione.

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

In thread1, non è necessario fare nulla di speciale, purché non provi a modificare il valore di a(il che creerebbe una variabile locale che ombreggia quella globale; usala global ase necessario)>

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

47

In una funzione:

a += 1

sarà interpretato dal compilatore come assign to a => Create local variable a, che non è quello che vuoi. Probabilmente fallirà con un a not initializederrore poiché il (locale) a non è stato effettivamente inizializzato:

>>> 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

Potresti ottenere ciò che desideri con la globalparola chiave (molto disapprovata e per buone ragioni) , in questo modo:

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

In generale, tuttavia, dovresti evitare di utilizzare variabili globali che diventano estremamente rapidamente fuori controllo. E questo è particolarmente vero per i programmi multithread, dove non hai alcun meccanismo di sincronizzazione per thread1sapere quando aè stato modificato. In breve: i thread sono complicati e non ci si può aspettare di avere una comprensione intuitiva dell'ordine in cui si verificano gli eventi quando due (o più) thread lavorano sullo stesso valore. Il linguaggio, il compilatore, il sistema operativo, il processore ... possono TUTTI svolgere un ruolo e decidere di modificare l'ordine delle operazioni per velocità, praticità o per qualsiasi altro motivo.

Il modo corretto per questo genere di cose è usare gli strumenti di condivisione di Python ( lucchetti e amici), o meglio, comunicare i dati tramite una coda invece di condividerli, ad esempio in questo modo:

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

Questo risolve un grosso problema. E sembra più o meno l'approccio giusto per affrontarlo.
Abhidemon

Questo è il modo in cui sto usando per risolvere il problema di sincronizzazione.
Zhang LongQI

1
Ho alcune domande Innanzitutto, se ho più variabili da condividere tra i thread, ho bisogno di una coda separata per ogni variabile? In secondo luogo, perché le code nel programma sopra sono sincronizzate? Ciascuno non dovrebbe agire come copia locale in ogni funzione?

Questo è vecchio, ma rispondo comunque. La coda stessa non è sincronizzata, non più della variabile a. È il comportamento di blocco predefinito della coda che crea la sincronizzazione. L'istruzione a = q.get()si bloccherà (attenderà) finché non sarà disponibile un valore a. La variabile qè locale: se le si assegna un valore diverso, avverrà solo localmente. Ma la coda assegnatagli nel codice è quella definita nel thread principale.

1
Non è sempre necessario utilizzare una coda per condividere le informazioni tra i thread. L'esempio nella risposta chepner va benissimo. Inoltre, una coda non è sempre lo strumento giusto. Una coda è utile, ad esempio, se si desidera bloccare fino a quando il valore non è disponibile. È inutile se i due thread competono su una risorsa condivisa. Infine, le variabili globali non sono peggiori nei thread. Possono essere infatti più naturali. Ad esempio, il tuo thread potrebbe essere solo un blocco di codice, diciamo un ciclo, che necessita del proprio processo. L'ambito locale viene quindi creato artificialmente quando si inserisce il ciclo in una funzione.

5

Un lucchetto dovrebbe essere considerato da usare, come threading.Lock. Vedi oggetti di blocco per maggiori informazioni.

La risposta accettata PU stampare 10 per thread1, che non è quello che vuoi. È possibile eseguire il codice seguente per comprendere più facilmente il bug.

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print "unreachable."

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

L'uso di un lucchetto può impedire il cambio di adurante la lettura più di una volta:

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

Se il thread utilizza la variabile per molto tempo, copiarla prima in una variabile locale è una buona scelta.


3

Grazie mille Jason Pan per aver suggerito quel metodo. L'istruzione if thread1 non è atomica, quindi mentre l'istruzione viene eseguita, è possibile che thread2 si intrometta nel thread1, consentendo di raggiungere il codice non raggiungibile. Ho organizzato le idee dei post precedenti in un programma dimostrativo completo (sotto) che ho eseguito con Python 2.7.

Con alcune analisi ponderate sono sicuro che potremmo ottenere ulteriori informazioni, ma per ora penso che sia importante dimostrare cosa succede quando il comportamento non atomico incontra il threading.

# 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

Come previsto, nell'esecuzione dell'esempio, a volte il codice "irraggiungibile" viene effettivamente raggiunto, producendo output.

Tanto per aggiungere, quando ho inserito una coppia di acquisizione / rilascio del blocco nel thread1 ho scoperto che la probabilità di avere la stampa del messaggio "non raggiungibile" era notevolmente ridotta. Per vedere il messaggio ho ridotto il tempo di sospensione a 0,01 sec e aumentato NN a 1000.

Con una coppia di acquisizione / rilascio del blocco nel thread1 non mi aspettavo di vedere il messaggio, ma è lì. Dopo aver inserito una coppia di acquisizione / rilascio blocco anche in thread2, il messaggio non è più apparso. In hind signt, anche l'istruzione increment in thread2 è probabilmente non atomica.


1
Hai bisogno dei blocchi in entrambi i thread perché questi sono cooperativi, "blocchi consultivi" (non "obbligatori"). Hai ragione nel dire che l'istruzione increment non è atomica.
Darkonaut

1

Bene, esempio in esecuzione:

AVVERTIMENTO! NON FARLO MAI A CASA / LAVORO! Solo in classe;)

Usa semafori, variabili condivise, ecc. Per evitare condizioni di punta.

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

e l'output:

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

Se il tempismo fosse corretto, l' a += 100operazione verrebbe saltata:

Processore esegue a T a+100e ottiene 104. Ma non si ferma, e salta alla discussione successiva Qui, a T + 1 esegue a+1con il vecchio valore di una, a == 4. Quindi calcola 5. Salta indietro (su T + 2), thread 1 e scrivi a=104in memoria. Ora torniamo al thread 2, il tempo è T + 3 e scrivi a=5in memoria. Ecco! La prossima istruzione di stampa stamperà 5 invece di 104.

Insetto MOLTO brutto da riprodurre e catturare.


Si prega di considerare anche l'aggiunta di un'implementazione corretta. Sarebbe molto utile per coloro che imparano a condividere i dati tra i thread.
JS.

1
Aggiunto alla lista "cose ​​da fare" :)
visoft
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.