Являются ли списки потокобезопасными? - PullRequest
126 голосов
/ 12 июня 2011

Я заметил, что часто предлагается использовать очереди с несколькими потоками вместо списков и .pop().Это потому, что списки не являются потокобезопасными или по какой-то другой причине?

Ответы [ 4 ]

151 голосов
/ 12 июня 2011

Списки сами по себе потокобезопасны.В CPython GIL защищает от одновременного доступа к ним, а другие реализации стараются использовать детальную блокировку или синхронизированный тип данных для своих реализаций списка.Однако, хотя списки сами по себе не могут испортиться при попытках одновременного доступа, данные списков не защищены.Например:

L[0] += 1

не гарантирует фактического увеличения L [0] на единицу, если другой поток делает то же самое, потому что += не является атомарной операцией.(Очень, очень мало операций в Python на самом деле являются атомарными, потому что большинство из них может вызвать вызов произвольного кода Python.) Вам следует использовать очереди, потому что, если вы просто используете незащищенный список, вы можете получить или удалить неправильный элемент из-за условий гонки.

76 голосов
/ 02 сентября 2013

Чтобы прояснить вопрос в превосходном ответе Томаса, следует упомянуть, что append() является потокобезопасным.

Это связано с тем, что нет никаких опасений, что данные, прочитанные , будут в том же месте, как только мы перейдем к записи в него. Операция append() не считывает данные, она только записывает данные в список.

34 голосов
/ 01 ноября 2013

Вот полный, но не исчерпывающий список примеров из list операций и того, являются ли они потокобезопасными Надеемся получить ответ относительно языковой конструкции obj in a_list здесь .

0 голосов
/ 06 апреля 2019

У меня недавно был этот случай, когда мне нужно было непрерывно добавлять в список в одном потоке, циклически проходить по элементам и проверять, готов ли элемент, это был AsyncResult в моем случае и удалять его из списка, только если он былготовы.Я не смог найти ни одного примера, который бы четко продемонстрировал мою проблему. Вот пример, демонстрирующий непрерывное добавление в список в одном потоке и постоянное удаление из этого же списка в другом потоке. Дефектная версия легко работает на меньших числах, но сохраняет числа достаточно большими и запускаетнесколько раз, и вы увидите ошибку

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)

Вывод при ОШИБКЕ

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

Версия, в которой используются блокировки

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)

Вывод

[] # Empty list

Вывод

Как уже упоминалосьв предыдущих ответах, в то время как процесс добавления или выталкивания элементов из самого списка является поточно-ориентированным, то, что не является поточно-безопасным, это когда вы добавляете в один поток и вставляете в другой

...