En una función:
a += 1
será interpretado por el compilador como assign to a => Create local variable a
, que no es lo que desea. Probablemente fallará con un a not initialized
error ya que el (local) a de hecho no se ha inicializado:
>>> 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
Puede obtener lo que desea con la global
palabra clave (muy mal vista y por buenas razones) , así:
>>> def f():
... global a
... a += 1
...
>>> a
1
>>> f()
>>> a
2
Sin embargo, en general, debe evitar el uso de variables globales que se salen de control con mucha rapidez. Y esto es especialmente cierto para los programas multiproceso, donde no tiene ningún mecanismo de sincronización para thread1
saber cuándo a
se ha modificado. En resumen: los hilos son complicados y no puede esperar tener una comprensión intuitiva del orden en el que ocurren los eventos cuando dos (o más) subprocesos funcionan con el mismo valor. El lenguaje, compilador, SO, procesador ... TODOS pueden jugar un papel, y decidir modificar el orden de las operaciones por velocidad, practicidad o cualquier otra razón.
La forma correcta para este tipo de cosas es usar las herramientas de intercambio de Python ( bloqueos
y amigos), o mejor, comunicar datos a través de una cola en lugar de compartirlos, por ejemplo, así:
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 el comportamiento de bloqueo predeterminado de la cola lo que crea la sincronización. La declaracióna = q.get()
se bloqueará (esperará) hasta que esté disponible un valor a. La variableq
es local: si le asigna un valor diferente, sucederá solo localmente. Pero la cola que se le asigna en el código es la que está definida en el hilo principal.Se debe considerar el uso de un candado, como
threading.Lock
. Consulte objetos de bloqueo para obtener más información.La respuesta aceptada PUEDE imprimir 10 por hilo1, que no es lo que desea. Puede ejecutar el siguiente código para comprender el error más fácilmente.
def thread1(threadname): while True: if a % 2 and not a % 2: print "unreachable." def thread2(threadname): global a while True: a += 1
El uso de un candado puede prohibir el cambio
a
mientras lee más de una vez: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 el hilo usa la variable durante mucho tiempo, es una buena opción adaptarla primero a una variable local.
fuente
Muchas gracias Jason Pan por sugerir ese método. La instrucción if thread1 no es atómica, de modo que mientras se ejecuta esa instrucción, es posible que thread2 se inmiscuya en thread1, permitiendo que se alcance el código no accesible. He organizado ideas de las publicaciones anteriores en un programa de demostración completo (a continuación) que ejecuté con Python 2.7.
Con un análisis cuidadoso, estoy seguro de que podríamos obtener más información, pero por ahora creo que es importante demostrar lo que sucede cuando el comportamiento no atómico se encuentra con los hilos.
# 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
Como se predijo, al ejecutar el ejemplo, el código "inalcanzable" a veces se alcanza realmente, produciendo una salida.
Solo para agregar, cuando inserté un par de adquisición / liberación de bloqueo en thread1, encontré que la probabilidad de que se imprimiera el mensaje "inalcanzable" se redujo considerablemente. Para ver el mensaje, reduje el tiempo de reposo a 0,01 segundos y aumenté NN a 1000.
Con un par de adquisición / liberación de bloqueo en thread1, no esperaba ver el mensaje en absoluto, pero está ahí. Después de insertar un par de adquisición / liberación de bloqueo también en thread2, el mensaje ya no apareció. En hind signt, la declaración de incremento en thread2 probablemente tampoco sea atómica.
fuente
Bueno, ejemplo de ejecución:
¡ADVERTENCIA! ¡NUNCA HAGA ESTO EN CASA / TRABAJO! Solo en el aula;)
Utilice semáforos, variables compartidas, etc. para evitar condiciones de prisa.
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()
y la salida:
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 el momento fuera el correcto, la
a += 100
operación se omitiría:El procesador se ejecuta en T
a+100
y obtiene 104. Pero se detiene y salta al siguiente hilo Aquí, En T + 1 se ejecutaa+1
con el valor anterior de aa == 4
,. Entonces calcula 5. Salta hacia atrás (en T + 2), subproceso 1 y escribea=104
en la memoria. Ahora, de vuelta en el hilo 2, el tiempo es T + 3 y se escribea=5
en la memoria. ¡Voila! La siguiente instrucción de impresión imprimirá 5 en lugar de 104.Error MUY desagradable para ser reproducido y atrapado.
fuente