web-dev-qa-db-fra.com

Utiliser une variable globale avec un thread

Comment partager une variable globale avec thread?

Mon exemple de code Python est le suivant:

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

Je ne sais pas comment faire en sorte que les deux threads partagent une variable.

67
Mauro Midolo

Vous devez simplement déclarer a en tant que global dans thread2, afin de ne pas modifier un a local pour cette fonction.

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

Dans thread1, vous n'avez rien de spécial à faire, tant que vous n'essayez pas de modifier la valeur de a (ce qui créerait une variable locale qui occulterait la variable globale; utilisez global a si vous en avez besoin)>

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

Dans une fonction:

a += 1

sera interprété par le compilateur comme assign to a => Create local variable a, ce qui n’est pas ce que vous voulez. Il échouera probablement avec une erreur a not initialized puisque le (local) a n'a en effet pas été initialisé:

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

Vous pouvez obtenir ce que vous voulez avec le mot clé (très mal vu et pour de bonnes raisons) global, comme ceci:

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

En général cependant, vous devriez éviter d’utiliser des variables globales qui deviennent extrêmement rapidement incontrôlables. Et ceci est particulièrement vrai pour les programmes multithreads, où vous n'avez aucun mécanisme de synchronisation pour que votre thread1 sache quand a a été modifié. En bref: les threads sont compliqués et vous ne pouvez pas vous attendre à une compréhension intuitive de l'ordre dans lequel les événements se produisent lorsque deux (ou plus) threads fonctionnent sur la même valeur. Le langage, le compilateur, le système d'exploitation, le processeur ... peuvent TOUT jouer un rôle et décider de modifier l'ordre des opérations pour des raisons de rapidité, de commodité ou pour toute autre raison.

La bonne façon de procéder consiste à utiliser Python outils de partage ( verrous et amis), ou mieux, à communiquer des données via un file d'attente au lieu de partage, par exemple comme ça:

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

Il faut envisager d'utiliser un verrou, tel que threading.Lock. Voir lock-objects pour plus d'informations.

La réponse acceptée PEUT imprimer 10 par le fil 1, ce qui n’est pas ce que vous voulez. Vous pouvez exécuter le code suivant pour comprendre le bogue plus facilement.

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

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

L'utilisation d'un verrou peut empêcher le changement de a lors de la lecture de plusieurs fois:

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

Si thread utilise la variable pendant une longue période, il est préférable d’en faire une variable locale.

4
Jason Pan

Merci beaucoup à Jason Pan d’avoir suggéré cette méthode. L'instruction thread1 si n'est pas atomique, de sorte que, même si cette instruction est exécutée, il est possible que thread2 s'immisce dans thread1, permettant ainsi d'atteindre un code inaccessible. J'ai organisé les idées des articles précédents dans un programme de démonstration complet (ci-dessous) que j'avais exécuté avec Python 2.7.

Avec une analyse réfléchie, je suis sûr que nous pourrions approfondir nos connaissances, mais pour le moment, je pense qu’il est important de démontrer ce qui se produit lorsque le comportement non atomique rencontre le filetage.

# 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

Comme prévu, en exécutant l'exemple, le code "inaccessible" est parfois réellement atteint, ce qui produit une sortie.

Ajoutons simplement que, lorsque j’ai inséré un verrou d’acquisition/libération dans thread1, j’ai constaté que la probabilité d’avoir le message "inaccessible" était considérablement réduite. Pour voir le message, j'ai réduit le temps de sommeil à 0,01 s et augmenté le nombre de NN à 1 000.

Avec un verrou paire acquisition/libération dans thread1, je ne m'attendais pas à voir le message du tout, mais c'est là. Après avoir inséré une paire verrouiller/relâcher également dans thread2, le message n'apparaissait plus. En hind signt, l'instruction d'incrémentation dans thread2 n'est probablement pas non plus atomique.

3
Krista M Hill

Eh bien, exemple courant:

ATTENTION! NE JAMAIS FAIRE CECI AT HOME/WORK! Seulement en classe;)

Utilisez des sémaphores, des variables partagées, etc. pour éviter les conditions de Rush.

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

et la sortie:

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

Si le timing était correct, l'opération a += 100 serait ignorée:

Le processeur s'exécute à T a+100 et obtient 104. Mais il s'arrête et passe au thread suivant Ici, à T + 1, exécute a+1 avec l'ancienne valeur de a, a == 4. Donc, il calcule 5. Revenez en arrière (à T + 2), enfilez 1 et écrivez a=104 en mémoire. Revenons maintenant au fil 2, le temps est T + 3 et écrivez a=5 en mémoire. Voila! La prochaine instruction d'impression imprimera 5 au lieu de 104.

TRÈS méchant insecte à reproduire et à attraper.

1
visoft