Ich stelle fest, dass häufig empfohlen wird, Warteschlangen mit mehreren Threads anstelle von Listen und zu verwenden .pop()
. Liegt das daran, dass Listen nicht threadsicher sind oder aus einem anderen Grund?
Ich stelle fest, dass häufig empfohlen wird, Warteschlangen mit mehreren Threads anstelle von Listen und zu verwenden .pop()
. Liegt das daran, dass Listen nicht threadsicher sind oder aus einem anderen Grund?
Antworten:
Listen selbst sind threadsicher. In CPython schützt die GIL vor gleichzeitigen Zugriffen auf sie, und andere Implementierungen achten darauf, für ihre Listenimplementierungen eine fein abgestimmte Sperre oder einen synchronisierten Datentyp zu verwenden. Obwohl Listen selbst nicht durch Versuche, gleichzeitig darauf zuzugreifen, beschädigt werden können, sind die Daten der Listen nicht geschützt. Beispielsweise:
L[0] += 1
Es wird nicht garantiert, dass L [0] tatsächlich um eins erhöht wird, wenn ein anderer Thread dasselbe tut, da +=
es sich nicht um eine atomare Operation handelt. (Sehr, sehr wenige Operationen in Python sind tatsächlich atomar, da die meisten dazu führen können, dass beliebiger Python-Code aufgerufen wird.) Sie sollten Warteschlangen verwenden, da Sie aufgrund der Rasse möglicherweise das falsche Element erhalten oder löschen, wenn Sie nur eine ungeschützte Liste verwenden Bedingungen.
Um einen Punkt in Thomas' ausgezeichneten Antwort zu klären, soll erwähnt werden , dass append()
ist Thread - sicher.
Dies liegt daran, dass keine Bedenken bestehen, dass sich die gelesenen Daten an derselben Stelle befinden, sobald wir sie schreiben . Die append()
Operation liest keine Daten, sondern schreibt nur Daten in die Liste.
PyList_Append
erfolgt in einer GIL-Sperre. Es wird ein Verweis auf ein Objekt zum Anhängen gegeben. Der Inhalt dieses Objekts kann nach der Auswertung und vor dem Aufruf von geändert werden PyList_Append
. Aber es wird immer noch dasselbe Objekt sein und sicher angehängt (wenn Sie dies tun lst.append(x); ok = lst[-1] is x
, ok
kann es natürlich falsch sein). Der Code, auf den Sie verweisen, liest nicht aus dem angehängten Objekt, außer um es zu ERHÖHEN. Es liest die angehängte Liste und kann sie neu zuordnen.
L[0] += x
ein durchführen wird __getitem__
auf L
und dann __setitem__
auf L
- wenn L
unterstützt __iadd__
es die Dinge ein wenig tun wird anders im Objekt - Schnittstelle, aber es gibt immer noch zwei getrennte Operationen L
an der Python - Interpreter Ebene (Sie werden sie in dem sehen kompilierter Bytecode). Das append
ist in aa einzigen Methodenaufruf im Bytecode getan.
remove
?
Hier ist eine umfassende, aber nicht erschöpfende Liste von Beispielen für list
Vorgänge und ob sie threadsicher sind oder nicht. Hoffnung , eine Antwort in Bezug auf die bekommen obj in a_list
Sprachkonstrukt hier .
Ich hatte kürzlich diesen Fall, in dem ich eine Liste kontinuierlich in einem Thread anhängen, die Elemente durchlaufen und prüfen musste, ob das Element bereit war. In meinem Fall war es ein AsyncResult und es nur dann aus der Liste entfernen, wenn es bereit war. Ich konnte keine Beispiele finden, die mein Problem klar demonstrierten. Hier ist ein Beispiel, das das kontinuierliche Hinzufügen zur Liste in einem Thread und das kontinuierliche Entfernen aus derselben Liste in einem anderen Thread demonstriert. Die fehlerhafte Version läuft problemlos mit kleineren Zahlen, aber halten Sie die Zahlen groß genug und führen Sie a aus einige Male und Sie werden den Fehler sehen
Die FLAWED-Version
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)
Ausgabe bei FEHLER
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
Version, die Sperren verwendet
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)
Ausgabe
[] # Empty list
Fazit
Wie in den früheren Antworten erwähnt, ist das Anhängen oder Löschen von Elementen aus der Liste selbst threadsicher. Was nicht threadsicher ist, ist, wenn Sie einen Thread anhängen und einen anderen einfügen
with r:
) zu verwenden, als explizit aufzurufen r.acquire()
undr.release()