W funkcji:
a += 1
zostanie zinterpretowany przez kompilator jako assign to a => Create local variable a
, co nie jest tym, czego chcesz. Prawdopodobnie zakończy się niepowodzeniem z a not initialized
błędem, ponieważ (lokalny) a rzeczywiście nie został zainicjowany:
>>> 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
Możesz dostać to, czego chcesz, ze global
słowem kluczowym (bardzo niezadowolonym i nie bez powodu) , na przykład:
>>> def f():
... global a
... a += 1
...
>>> a
1
>>> f()
>>> a
2
Generalnie jednak należy unikać używania zmiennych globalnych, które bardzo szybko wymykają się spod kontroli. Jest to szczególnie ważne w przypadku programów wielowątkowych, w których nie ma żadnego mechanizmu synchronizacji, thread1
aby wiedzieć, kiedy a
został zmodyfikowany. Krótko mówiąc: wątki są skomplikowane i nie można oczekiwać intuicyjnego zrozumienia kolejności, w jakiej zachodzą zdarzenia, gdy dwa (lub więcej) wątki działają na tej samej wartości. Język, kompilator, system operacyjny, procesor ... WSZYSTKIE mogą odgrywać rolę i decydować o zmianie kolejności operacji ze względu na szybkość, praktyczność lub z jakiegokolwiek innego powodu.
Właściwym sposobem na tego typu rzeczy jest użycie narzędzi do udostępniania w Pythonie ( blokad
i znajomych) lub lepiej, przesyłanie danych za pośrednictwem kolejki zamiast ich udostępniania, np. W ten sposób:
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
. Jest to domyślne zachowanie blokujące kolejki, które tworzy synchronizację. Instrukcjaa = q.get()
będzie blokować (czekać), aż wartość a będzie dostępna. Zmiennaq
jest lokalna: jeśli przypiszesz jej inną wartość, stanie się to tylko lokalnie. Ale kolejka przypisana do niego w kodzie to ta, która jest zdefiniowana w głównym wątku.Należy rozważyć użycie blokady, na przykład
threading.Lock
. Aby uzyskać więcej informacji, zobacz obiekty zamków .Zaakceptowana odpowiedź MOŻE wydrukować 10 wątkiem1, co nie jest tym, czego chcesz. Możesz uruchomić poniższy kod, aby łatwiej zrozumieć błąd.
def thread1(threadname): while True: if a % 2 and not a % 2: print "unreachable." def thread2(threadname): global a while True: a += 1
Używanie zamka może uniemożliwić zmianę
a
podczas czytania więcej niż jeden raz: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()
Jeśli wątek używa zmiennej przez długi czas, dobrym wyborem jest najpierw skopiowanie jej do zmiennej lokalnej.
źródło
Dziękuję bardzo Jason Pan za zasugerowanie tej metody. Instrukcja thread1 if nie jest atomowa, więc gdy ta instrukcja jest wykonywana, możliwe jest, aby thread2 włamał się do thread1, umożliwiając osiągnięcie nieosiągalnego kodu. Zorganizowałem pomysły z poprzednich postów w kompletny program demonstracyjny (poniżej), który uruchomiłem z Pythonem 2.7.
Jestem pewien, że dzięki dogłębnej analizie moglibyśmy uzyskać dalsze informacje, ale na razie myślę, że ważne jest, aby pokazać, co się dzieje, gdy zachowanie nieatomowe spotyka się z wątkami.
# 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
Zgodnie z przewidywaniami, podczas uruchamiania przykładu czasami dochodzi do „nieosiągalnego” kodu, co daje wynik.
Dodam, że kiedy wstawiłem parę nabycie / zwolnienie blokady do thread1, stwierdziłem, że prawdopodobieństwo wydrukowania komunikatu „nieosiągalny” zostało znacznie zmniejszone. Aby zobaczyć komunikat, zredukowałem czas uśpienia do 0,01 sekundy i zwiększyłem NN do 1000.
Z parą pozyskiwania / zwalniania blokady w wątku 1 nie spodziewałem się, że w ogóle zobaczę komunikat, ale on tam jest. Po wstawieniu pary nabycia / zwolnienia blokady również do Thread2 komunikat przestał się pojawiać. W signt tylnej instrukcja inkrementacji w wątku2 prawdopodobnie również nie jest atomowa.
źródło
Cóż, działający przykład:
OSTRZEŻENIE! NIGDY NIE ROBIĆ TEGO W DOMU / PRACY! Tylko w klasie;)
Używaj semaforów, wspólnych zmiennych itp., Aby uniknąć nagłych warunków.
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()
i wyjście:
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
Gdyby czas był właściwy,
a += 100
operacja zostałaby pominięta:Procesor wykonuje w punkcie T
a+100
i uzyskuje 104. Ale zatrzymuje się i przeskakuje do następnego wątku. Tutaj w punkcie T + 1 wykonuje sięa+1
ze starą wartością aa == 4
,. Więc oblicza 5. Skocz wstecz (w T + 2), wątek 1 i zapisza=104
w pamięci. Wracając do wątku 2, czas wynosi T + 3 i zapisza=5
do pamięci. Voila! Następna instrukcja drukowania wypisze 5 zamiast 104.BARDZO okropny błąd do odtworzenia i złapania.
źródło