Zauważyłem, że często sugeruje się używanie kolejek z wieloma wątkami zamiast list i .pop()
. Czy dzieje się tak, ponieważ listy nie są bezpieczne dla wątków lub z innego powodu?
Zauważyłem, że często sugeruje się używanie kolejek z wieloma wątkami zamiast list i .pop()
. Czy dzieje się tak, ponieważ listy nie są bezpieczne dla wątków lub z innego powodu?
Odpowiedzi:
Same listy są bezpieczne dla wątków. W CPythonie GIL chroni przed równoczesnym dostępem do nich, a inne implementacje dbają o użycie precyzyjnej blokady lub zsynchronizowanego typu danych dla ich implementacji list. Jednakże, podczas gdy list sami nie mogą przejść przez uszkodzony prób do równoczesnego dostępu listach męska dane nie są chronione. Na przykład:
L[0] += 1
nie ma gwarancji, że faktycznie zwiększy L [0] o jeden, jeśli inny wątek zrobi to samo, ponieważ +=
nie jest to operacja atomowa. (Bardzo, bardzo niewiele operacji w Pythonie jest w rzeczywistości atomowych, ponieważ większość z nich może spowodować wywołanie dowolnego kodu w Pythonie). Powinieneś używać kolejek, ponieważ jeśli używasz tylko niezabezpieczonej listy, możesz uzyskać lub usunąć niewłaściwy element z powodu rasy warunki.
Aby wyjaśnić punkt w doskonałej odpowiedzi Thomasa, należy wspomnieć, że append()
jest bezpieczny dla wątków.
Dzieje się tak, ponieważ nie ma obaw, że odczytywane dane będą w tym samym miejscu, gdy zaczniemy je pisać . append()
Operacja nie odczytuje dane, to tylko zapisuje je na liście.
PyList_Append
odbywa się w jednym zamku GIL. Otrzymuje odniesienie do obiektu do dołączenia. Zawartość tego obiektu może ulec zmianie po jego ocenie i przed wykonaniem wywołania PyList_Append
. Ale nadal będzie to ten sam obiekt i bezpiecznie dołączony (jeśli to zrobisz lst.append(x); ok = lst[-1] is x
, ok
może to być oczywiście Fałsz). Kod, do którego się odwołujesz, nie czyta z dołączonego obiektu, z wyjątkiem jego ZWIĘKSZENIA. Czyta listę, do której jest dołączona, i może ją ponownie przydzielić.
L[0] += x
wykona __getitem__
on L
a potem __setitem__
na L
- jeśli L
podpory __iadd__
będzie robić rzeczy trochę inaczej na styku obiektu, ale nadal istnieją dwie odrębne operacje na L
na poziomie interpreter Pythona (widać je w kompilowany kod bajtowy). append
Odbywa się aa pojedynczego wywołania metody w kodu bajtowego.
remove
?
Oto niepełna jeszcze niewyczerpujący wykaz przykładów z list
działalności i czy nie są one bezpieczne dla wątków. Mając nadzieję na uzyskanie odpowiedzi dotyczącej obj in a_list
konstruktem językowym tutaj .
Niedawno miałem taki przypadek, w którym musiałem stale dołączać do listy w jednym wątku, przeglądać elementy w pętli i sprawdzać, czy element był gotowy, w moim przypadku był to AsyncResult i usunąć go z listy tylko wtedy, gdy był gotowy. Nie mogłem znaleźć żadnych przykładów, które jasno pokazałyby mój problem Oto przykład pokazujący ciągłe dodawanie do listy w jednym wątku i ciągłe usuwanie z tej samej listy w innym wątku. Wadliwa wersja działa łatwo na mniejszych liczbach, ale utrzymuj liczby wystarczająco duże i uruchom kilka razy, a zobaczysz błąd
Wersja USZKODZONA
import threading
import time
# Change this number as you please, bigger numbers will get the error quickly
count = 1000
l = []
def add():
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Wyjście, gdy ERROR
Exception in thread Thread-63:
Traceback (most recent call last):
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-30-ecfbac1c776f>", line 13, in remove
l.remove(i)
ValueError: list.remove(x): x not in list
Wersja wykorzystująca zamki
import threading
import time
count = 1000
l = []
lock = threading.RLock()
def add():
with lock:
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
with lock:
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Wynik
[] # Empty list
Wniosek
Jak wspomniano we wcześniejszych odpowiedziach, podczas gdy czynność dołączania lub wyskakiwania elementów z samej listy jest bezpieczna dla wątków, to, co nie jest bezpieczne dla wątków, to dołączanie do jednego wątku i wstawianie do innego
with r:
) zamiast jawnie dzwonić r.acquire()
ir.release()