web-dev-qa-db-fra.com

Les listes sont-elles thread-safe?

Je remarque qu'il est souvent suggéré d'utiliser des files d'attente avec plusieurs threads au lieu de listes et de .pop(). Est-ce parce que les listes ne sont pas thread-safe ou pour une autre raison?

131
lemiant

Les listes elles-mêmes sont thread-safe. Dans CPython, GIL protège contre les accès simultanés, et d'autres implémentations veillent à utiliser un verrou à grain fin ou un type de données synchronisé pour leurs implémentations de liste. Cependant, bien que les listes elles-mêmes ne puissent pas être corrompues par des tentatives d’accès simultané, les listes données ne sont pas protégées. Par exemple:

L[0] += 1

il n'est pas garanti que L [0] augmente réellement de un si un autre thread fait la même chose, car += n'est pas une opération atomique. (Très, très peu d'opérations dans Python sont réellement atomiques, car la plupart d'entre elles peuvent provoquer l'arbitrage Python à appeler.). Vous devez utiliser Queues car vous utilisez simplement une liste non protégée, vous pouvez obtenir ou supprimer le mauvais article en raison de conditions de concurrence.

158
Thomas Wouters

Pour clarifier un point de l'excellente réponse de Thomas, il convient de mentionner que append()is thread safe.

C'est parce qu'il n'y a aucune inquiétude sur le fait que les données lire seront au même endroit une fois que nous irons à écrire. L'opération append() ne lit pas les données, elle les écrit uniquement dans la liste.

78
dotancohen

Voici une liste d’exemples exhaustive mais non exhaustive des list opérations et si elles sont ou non thread-safe. Espérant avoir une réponse concernant le obj in a_list langage construit ici .

34
Jonathan

J'ai récemment eu ce cas où je devais ajouter continuellement une liste à une liste dans un fil, parcourir les éléments et vérifier si l'élément était prêt, il s'agissait d'un AsyncResult dans mon cas et le supprimer de la liste uniquement s'il était prêt. Je n'ai pas trouvé d'exemple qui montre clairement mon problème. Voici un exemple montrant comment ajouter continuellement à la liste dans un fil et à supprimer continuellement de la même liste dans un autre fil. La version défectueuse fonctionne facilement avec des nombres plus petits, quelques fois et vous verrez l'erreur

La version 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)

sortie en cas d'erreur

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 utilisant des verrous

import threading
import time
count = 1000
l = []
r = threading.RLock()
def add():
    r.acquire()
    for i in range(count):
        l.append(i)
        time.sleep(0.0001)
    r.release()

def remove():
    r.acquire()
    for i in range(count):
        l.remove(i)
        time.sleep(0.0001)
    r.release()


t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()

print(l)

sortie

[] # Empty list

Conclusion

Comme mentionné dans les réponses précédentes, alors que l'acte d'ajouter ou de supprimer des éléments de la liste elle-même est thread-safe, ce qui n'est pas thread-safe est d'ajouter un fil à un fil et d'en insérer un autre.

2
PirateApp