Ho notato che viene spesso suggerito di utilizzare le code con più thread, anziché elenchi e .pop()
. Questo perché gli elenchi non sono thread-safe o per qualche altro motivo?
Ho notato che viene spesso suggerito di utilizzare le code con più thread, anziché elenchi e .pop()
. Questo perché gli elenchi non sono thread-safe o per qualche altro motivo?
Risposte:
Le liste stesse sono thread-safe. In CPython il GIL protegge da accessi simultanei ad essi e altre implementazioni si occupano di utilizzare un blocco a grana fine o un tipo di dati sincronizzato per le loro implementazioni di elenchi. Tuttavia, mentre gli elenchi stessi non possono essere danneggiati dai tentativi di accesso simultaneo, i dati degli elenchi non sono protetti. Per esempio:
L[0] += 1
non è garantito che aumenti effettivamente L [0] di uno se un altro thread fa la stessa cosa, perché +=
non è un'operazione atomica. (Pochissime operazioni in Python sono in realtà atomiche, poiché la maggior parte di esse può causare la chiamata di codice Python arbitrario.) Dovresti usare le code perché se usi solo un elenco non protetto, potresti ottenere o eliminare l'elemento sbagliato a causa della razza condizioni.
Per chiarire un punto dell'eccellente risposta di Thomas, è necessario ricordare che append()
è sicuro per i thread.
Questo perché non vi è alcuna preoccupazione che i dati letti saranno nello stesso posto una volta che andremo a scriverli . L' append()
operazione non legge i dati, scrive solo i dati nell'elenco.
PyList_Append
viene eseguita in un blocco GIL. Viene fornito un riferimento a un oggetto da aggiungere. Il contenuto di quell'oggetto potrebbe essere modificato dopo che è stato valutato e prima che PyList_Append
venga effettuata la chiamata . Ma sarà comunque lo stesso oggetto e aggiunto in modo sicuro (se lo fai lst.append(x); ok = lst[-1] is x
, allora ok
potrebbe essere Falso, ovviamente). Il codice a cui fai riferimento non legge dall'oggetto allegato, tranne per aumentarlo. Legge e può riallocare l'elenco a cui è stato aggiunto.
L[0] += x
eseguirà un __getitem__
on L
e poi un __setitem__
on L
- se L
supportato __iadd__
farà le cose in modo un po 'diverso nell'interfaccia dell'oggetto, ma ci sono ancora due operazioni separate L
a livello di interprete python (le vedrai nel compilato bytecode). Il append
è fatto in aa metodo singola chiamata nel bytecode.
remove
?
Ecco un elenco completo ma non esaustivo di esempi di list
operazioni e se sono sicuri per il thread. Spero di ottenere una risposta riguardo al obj in a_list
costrutto linguistico qui .
Recentemente ho avuto questo caso in cui dovevo aggiungere continuamente un elenco in un thread, scorrere gli elementi e controllare se l'articolo era pronto, era un AsyncResult nel mio caso e rimuoverlo dall'elenco solo se era pronto. Non sono riuscito a trovare alcun esempio che dimostrasse chiaramente il mio problema Ecco un esempio che dimostra l'aggiunta continua all'elenco in un thread e la rimozione continua dallo stesso elenco in un altro thread La versione difettosa viene eseguita facilmente su numeri più piccoli ma mantiene i numeri abbastanza grandi ed esegue un alcune volte e vedrai l'errore
La versione FLAWED
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)
Uscita quando ERRORE
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
Versione che utilizza i blocchi
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)
Produzione
[] # Empty list
Conclusione
Come menzionato nelle risposte precedenti mentre l'atto di aggiungere o estrarre elementi dall'elenco stesso è thread-safe, ciò che non è thread-safe è quando si aggiunge un thread e si apre un altro
with r:
) invece di chiamare esplicitamente r.acquire()
er.release()