Являются ли списки потокобезопасными? - программирование
Подтвердить что ты не робот

Являются ли списки потокобезопасными?

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

4b9b3361

Ответ 1

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

L[0] += 1

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

Ответ 2

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

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

Ответ 4

У меня недавно был этот случай, когда мне нужно было непрерывно добавлять в список в одном потоке, циклически проходить по элементам и проверять, готов ли элемент, в моем случае это был 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