В функции:
a += 1
будет интерпретироваться компилятором как assign to a => Create local variable a
, что вам не нужно. Вероятно, произойдет сбой с a not initialized
ошибкой, поскольку (локальный) a действительно не был инициализирован:
>>> 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
Вы можете получить то, что хотите, с global
ключевым словом (очень неодобрительно и по уважительным причинам) , например:
>>> def f():
... global a
... a += 1
...
>>> a
1
>>> f()
>>> a
2
В целом, однако, вам следует избегать использования глобальных переменных, которые очень быстро выходят из-под контроля. И это особенно верно для многопоточных программ, где у вас нет механизма синхронизации, чтобы вы thread1
знали, когда a
он был изменен. Вкратце: потоки сложны , и вы не можете ожидать интуитивного понимания порядка, в котором происходят события, когда два (или более) потока работают с одним и тем же значением. Язык, компилятор, ОС, процессор ... ВСЕ могут играть роль и принимать решение об изменении порядка операций для скорости, практичности или по любой другой причине.
Правильный способ для такого рода вещей - использовать инструменты совместного использования Python ( блокировки
и друзья) или, что еще лучше, передавать данные через очередь вместо того, чтобы делиться ими, например, вот так:
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
. Синхронизация создается по умолчанию при блокировании очереди. Операторa = q.get()
будет блокироваться (ждать), пока не станет доступно значение a. Переменнаяq
является локальной: если вы присвоите ей другое значение, это произойдет только локально. Но очередь, назначенная ему в коде, - это та, которая определена в основном потоке.Следует рассмотреть возможность использования блокировки, например
threading.Lock
. Смотрите lock-objects для получения дополнительной информации.Принятый ответ МОЖЕТ напечатать 10 с помощью thread1, что вам не нужно. Вы можете запустить следующий код, чтобы легче понять ошибку.
def thread1(threadname): while True: if a % 2 and not a % 2: print "unreachable." def thread2(threadname): global a while True: a += 1
Использование замка может запретить замену
a
при чтении более одного раза: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()
Если поток использует переменную в течение длительного времени, хорошим выбором будет сначала скопировать ее в локальную переменную.
источник
Большое спасибо Джейсону Пэну за предложение этого метода. Оператор thread1 if не является атомарным, поэтому, пока этот оператор выполняется, поток 2 может вторгнуться в поток 1, позволяя достичь недостижимого кода. Я организовал идеи из предыдущих сообщений в полную демонстрационную программу (ниже), которую я запускал с Python 2.7.
Я уверен, что с помощью некоторого вдумчивого анализа мы могли бы получить дальнейшее понимание, но пока я думаю, что важно продемонстрировать, что происходит, когда неатомарное поведение встречается с потоками.
# 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
Как и предполагалось, при выполнении примера иногда действительно достигается "недостижимый" код, что дает результат.
Просто чтобы добавить, когда я вставил пару получения / снятия блокировки в thread1, я обнаружил, что вероятность получения сообщения о «недоступности» значительно снизилась. Чтобы увидеть сообщение, я уменьшил время сна до 0,01 секунды и увеличил NN до 1000.
С парой получения / снятия блокировки в потоке 1 я вообще не ожидал увидеть сообщение, но оно есть. После того, как я вставил пару получения / снятия блокировки также в thread2, сообщение больше не появлялось. В заднем сигнале оператор приращения в thread2, вероятно, также не является атомарным.
источник
Ну, рабочий пример:
ПРЕДУПРЕЖДЕНИЕ! НИКОГДА НЕ ДЕЛАЙТЕ ЭТО ДОМА / НА РАБОТЕ!Только в классе;)
Используйте семафоры, общие переменные и т. Д., Чтобы избежать срочных условий.
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()
и вывод:
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
Если время было правильным,
a += 100
операция была бы пропущена:Процессор выполняется в T
a+100
и получает 104. Но он останавливается и переходит к следующему потоку. Здесь At T + 1 выполняетсяa+1
со старым значением aa == 4
,. Таким образом, он вычисляет 5. Обратный переход (при T + 2), поток 1 и записьa=104
в память. Теперь вернемся к потоку 2, время T + 3 и напишитеa=5
в память. Вуаля! Следующая инструкция печати напечатает 5 вместо 104.ОЧЕНЬ неприятная ошибка, которую нужно воспроизвести и отловить.
источник