Я заметил, что часто предлагается использовать очереди с несколькими потоками вместо списков и .pop()
. Это потому, что списки не являются потокобезопасными или по какой-то другой причине?
155
Я заметил, что часто предлагается использовать очереди с несколькими потоками вместо списков и .pop()
. Это потому, что списки не являются потокобезопасными или по какой-то другой причине?
Ответы:
Сами списки являются потокобезопасными. В CPython GIL защищает от одновременного доступа к ним, а другие реализации стараются использовать детализированную блокировку или синхронизированный тип данных для своих реализаций списка. Однако, хотя сами списки не могут испортиться при попытках одновременного доступа, данные списков не защищены. Например:
не гарантируется фактическое увеличение L [0] на единицу, если другой поток делает то же самое, потому что
+=
это не атомарная операция. (Очень, очень мало операций в Python на самом деле являются атомарными, потому что большинство из них может вызвать вызов произвольного кода Python.) Вам следует использовать очереди, потому что, если вы просто используете незащищенный список, вы можете получить или удалить неправильный элемент из-за гонки. условия.источник
Чтобы прояснить вопрос в превосходном ответе Томаса, следует упомянуть, что он
append()
является потокобезопасным.Это связано с тем, что не нужно беспокоиться о том, что читаемые данные будут в одном и том же месте, как только мы перейдем к записи в них.
append()
Операция не читает данные, он только записывает данные в список.источник
PyList_Append
выполняется в одном GIL-замке. Дается ссылка на объект для добавления. Содержимое этого объекта может быть изменено после его оценки и перед выполнением вызоваPyList_Append
. Но это все равно будет тот же объект, и он будет безопасно добавлен (если вы это сделаетеlst.append(x); ok = lst[-1] is x
, тоok
, конечно , может быть ложным). Код, на который вы ссылаетесь, не читает из добавленного объекта, кроме как для его INCREF. Он читает и может перераспределять список, к которому добавляется.L[0] += x
будет выполнять__getitem__
включение,L
а затем__setitem__
включениеL
- еслиL
поддерживает, то__iadd__
это будет немного по-другому работать на объектном интерфейсе, ноL
на уровне интерпретатора python по- прежнему выполняются две отдельные операции (вы увидите их в скомпилированный байт-код). Этоappend
делается одним вызовом метода в байт-коде.remove
?Вот всеобъемлющий еще не исчерпывающий перечень примеров из
list
операций и действительно ли они являются поточно. Надеемся получить ответ относительноobj in a_list
языковой конструкции здесь .источник
У меня недавно был этот случай, когда мне нужно было непрерывно добавлять в список в одном потоке, циклически проходить по элементам и проверять, готов ли элемент, в моем случае это был AsyncResult и удалять его из списка, только если он был готов. Я не смог найти ни одного примера, который бы четко продемонстрировал мою проблему. Вот пример, демонстрирующий непрерывное добавление в список в одном потоке и непрерывное удаление из этого же списка в другом потоке. Дефектная версия легко работает на меньших числах, но при этом сохраняет достаточно большие числа и запускает несколько раз, и вы увидите ошибку
FLAWED версия
Вывод при ОШИБКЕ
Версия, которая использует блокировки
Вывод
Вывод
Как упоминалось в предыдущих ответах, в то время как процесс добавления или выталкивания элементов из самого списка является потокобезопасным, не является безопасным поток, когда вы добавляете в один поток и вставляете в другой
источник
with r:
) вместо явного вызоваr.acquire()
иr.release()