Je remarque qu'il est souvent suggéré d'utiliser des files d'attente avec plusieurs threads, au lieu de listes et .pop()
. Est-ce parce que les listes ne sont pas thread-safe ou pour une autre raison?
155
Je remarque qu'il est souvent suggéré d'utiliser des files d'attente avec plusieurs threads, au lieu de listes et .pop()
. Est-ce parce que les listes ne sont pas thread-safe ou pour une autre raison?
Réponses:
Les listes elles-mêmes sont thread-safe. Dans CPython, le GIL protège contre les accès simultanés à ceux-ci, et d'autres implémentations prennent soin d'utiliser un verrou à granularité fine 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 données des listes ne sont pas protégées. Par exemple:
n'est pas garanti d'augmenter réellement L [0] de un si un autre thread fait la même chose, car ce
+=
n'est pas une opération atomique. (Très, très peu d'opérations en Python sont en fait atomiques, car la plupart d'entre elles peuvent provoquer l'appel de code Python arbitraire.) Vous devez utiliser des files d'attente car si vous utilisez simplement une liste non protégée, vous pouvez obtenir ou supprimer le mauvais élément en raison de la race conditions.la source
Pour clarifier un point de l'excellente réponse de Thomas, il convient de mentionner qu'elle
append()
est thread-safe.En effet, rien ne craint que les données en cours de lecture soient au même endroit une fois que nous y écrivons . L'
append()
opération ne lit pas les données, elle écrit uniquement les données dans la liste.la source
PyList_Append
est effectué dans un verrou GIL. Il reçoit une référence à un objet à ajouter. Le contenu de cet objet peut être modifié après son évaluation et avant que l'appel àPyList_Append
soit effectué. Mais ce sera toujours le même objet, et ajouté en toute sécurité (si vous le faiteslst.append(x); ok = lst[-1] is x
, celaok
peut être faux, bien sûr). Le code que vous référencez ne lit pas à partir de l'objet ajouté, sauf pour INCREF. Il lit, et peut réallouer, la liste qui est annexée.L[0] += x
effectuera un__getitem__
onL
, puis un__setitem__
onL
- si leL
supporte,__iadd__
cela fera les choses un peu différemment au niveau de l'interface objet, mais il y a toujours deux opérations séparées auL
niveau de l'interpréteur python (vous les verrez dans le bytecode compilé). Leappend
se fait dans un seul appel de méthode dans le bytecode.remove
?Voici une liste complète mais non exhaustive d'exemples d'
list
opérations et de savoir si elles sont thread-safe ou non. En espérant obtenir une réponse concernant laobj in a_list
construction du langage ici .la source
J'ai récemment eu ce cas où je devais ajouter à une liste en continu dans un fil, parcourir les éléments et vérifier si l'élément était prêt, c'était un AsyncResult dans mon cas et le supprimer de la liste uniquement s'il était prêt. Je n'ai trouvé aucun exemple illustrant clairement mon problème Voici un exemple montrant l'ajout à la liste dans un thread en continu et la suppression de la même liste dans un autre thread en continu La version défectueuse fonctionne facilement sur des nombres plus petits mais gardez les nombres suffisamment grands et exécutez un quelques fois et vous verrez l'erreur
La version FLAWED
Sortie lorsque ERROR
Version qui utilise des verrous
Production
Conclusion
Comme mentionné dans les réponses précédentes, alors que le fait d'ajouter ou de faire sauter des éléments de la liste elle-même est thread-safe, ce qui n'est pas thread-safe, c'est lorsque vous ajoutez un thread et en insérez un autre
la source
with r:
) au lieu d'appeler explicitementr.acquire()
etr.release()