Ответ
В Python список (list) не является потокобезопасным. Это означает, что если несколько потоков одновременно изменяют список (например, добавляют или удаляют элементы), это может привести к состоянию гонки (race condition), повреждению данных или неожиданному поведению. Это происходит потому, что операции со списком не являются атомарными.
Пример проблемы (состояние гонки):
import threading
shared_list = []
def add_items():
for i in range(1000):
shared_list.append(i)
threads = []
for _ in range(10):
t = threading.Thread(target=add_items)
threads.append(t)
t.start()
for t in threads:
t.join()
# Ожидаемый размер: 10 * 1000 = 10000
# Фактический размер может быть меньше из-за состояний гонки
print(len(shared_list))
Решение для потокобезопасной работы:
-
Использование
threading.Lock: Это самый распространенный способ синхронизации доступа к общим ресурсам. Блокировка гарантирует, что только один поток может выполнять критическую секцию кода в любой момент времени.import threading lock = threading.Lock() shared_list = [] def add_items_safe(): for i in range(1000): with lock: # Захватываем блокировку перед изменением списка shared_list.append(i) threads = [] for _ in range(10): t = threading.Thread(target=add_items_safe) threads.append(t) t.start() for t in threads: t.join() print(len(shared_list)) # Будет 10000 -
Использование
queue.Queue: Если требуется потокобезопасная структура данных с логикой FIFO (первым пришел — первым ушел),queue.Queueиз модуляqueueявляется отличным выбором, так как он изначально разработан для потокобезопасного обмена данными между потоками. -
Для межпроцессного взаимодействия: Для обмена данными между процессами (не потоками) можно использовать
multiprocessing.Manager().list()или другие средства синхронизации из модуляmultiprocessing.
Ответ 18+ 🔞
А, слушай, смотри, вот эта вся история с потоками и списками в питоне — это вообще отдельный цирк с конями, блядь. Представь себе: у тебя есть список, обычный такой, list, вроде всё мирно, да? А потом ты запускаешь на него десять потоков, и они все как с цепи сорвутся — начинают туда пихать свои элементы одновременно. И тут, сука, начинается такое...
Это ж как на толкучке, когда все лезут в одну сумку, а в итоге половина вещей мимо падает, и в сумке нихуя не остаётся, понимаешь? Вот и тут так же. Ожидаешь ты, что десять потоков по тысяче элементов каждый — это десять тысяч, блядь, а на выходе получаешь, например, девять с половиной, а то и меньше. Потому что операции-то не атомарные, ёпта! Они там друг другу нахуй наступают, перезаписывают индексы, и в итоге — пиздец, данные похерились.
Вот смотри, какой пиздец может выйти:
import threading
shared_list = []
def add_items():
for i in range(1000):
shared_list.append(i)
threads = []
for _ in range(10):
t = threading.Thread(target=add_items)
threads.append(t)
t.start()
for t in threads:
t.join()
# Ожидаемый размер: 10 * 1000 = 10000
# Фактический размер может быть меньше из-за состояний гонки
print(len(shared_list))
Запустишь это — и волнение ебать, с каждым разом разный результат, как в лотерее, блядь! То девять тысяч, то восемь, а то и вообще какая-нибудь дичь.
Так что же делать, спросишь ты? А выход есть, не ссы!
Первый и самый главный способ — threading.Lock. Это как такой здоровенный мужик с дубиной у входа в список стоит. Хочешь зайти что-то добавить или удалить — получи, блядь, разрешение. Один вошёл — все остальные ждут, пока он не выйдет. Порядок, сука, железный!
import threading
lock = threading.Lock()
shared_list = []
def add_items_safe():
for i in range(1000):
with lock: # Захватываем блокировку перед изменением списка
shared_list.append(i)
threads = []
for _ in range(10):
t = threading.Thread(target=add_items_safe)
threads.append(t)
t.start()
for t in threads:
t.join()
print(len(shared_list)) # Будет 10000
Вот теперь, сука, всё чётко. Все десять тысяч на месте, как штыки. Блокировка — наше всё, ебать мои старые костыли.
Второй вариант — queue.Queue. Это если тебе нужна не просто куча данных, а чтобы они ещё и в порядке приходили — кто первый пришёл, тот первый и ушёл. Эта штука из коробки потокобезопасная, там внутри всё уже прикручено. Просто бери и пользуйся, не еби мозг.
Ну и если ты совсем упоротый и работаешь с процессами, а не потоками (это, кстати, другая песня, там память отдельная у каждого), то тут уже multiprocessing.Manager().list() в помощь. Но это уже, как говорится, совсем другая история, в рот меня чих-пых.
Короче, суть в чём: со списками в потоках шутить не надо, а то получишь не данные, а сплошное расстройство и недосчёт. Либо блокировку ставь, либо очередь используй — и спи спокойно.