Я зауважую, що часто пропонується використовувати черги з декількома потоками замість списків та .pop()
. Це тому, що списки не є безпечними для потоків чи з якоїсь іншої причини?
Я зауважую, що часто пропонується використовувати черги з декількома потоками замість списків та .pop()
. Це тому, що списки не є безпечними для потоків чи з якоїсь іншої причини?
Відповіді:
Самі списки є безпечними для ниток. У CPython GIL захищає від одночасного доступу до них, а інші реалізації дбають про використання дрібнозернистого блокування або синхронізованого типу даних для їх реалізації списків. Однак, в той час як списки сам не може йти ксебе спроб отримати одночасний доступ, списки ігрових дані не захищені. Наприклад:
L[0] += 1
не гарантується фактично збільшити L [0] на одну, якщо інша нитка зробить те саме, оскільки +=
це не атомна операція. (Дуже мало операцій в Python насправді є атомними, оскільки більшість з них може викликати виклик довільного коду Python.) Ви повинні використовувати черги, оскільки якщо ви просто використовуєте незахищений список, ви можете отримати або видалити неправильний елемент через гонку умови.
Щоб уточнити кращу відповідь Томаса, слід зазначити, що append()
це безпечно для ниток.
Це тому, що немає ніякого занепокоєння, що дані, які читаються, будуть там же, коли ми підемо в них записувати . append()
Операція не читає дані, він тільки записує дані в список.
PyList_Append
робиться в одному блоці GIL. Дається посилання на об'єкт для додавання. Вміст цього об'єкта може бути змінено після його оцінювання та перед тим, як здійснити виклик PyList_Append
. Але це все одно буде той самий об’єкт і надійно доданий (якщо ви це зробите lst.append(x); ok = lst[-1] is x
, то ok
, звичайно , може бути помилковим). Код, на який ви посилаєтесь, не зчитується з доданого об'єкта, за винятком того, що він НЕ ВКЛЮЧИТЬ його. Він читає та може перерозподілити список, до якого додається.
L[0] += x
виконає __getitem__
на , L
а потім __setitem__
на L
- якщо L
підтримує __iadd__
це буде робити речі трохи по- іншому на кордоні об'єкта, але є ще дві операції на L
на рівні пітон інтерпретатора (ви будете бачити їх у складений байт-код). append
Робиться в аа один виклик методу в байткод.
remove
?
Ось всеосяжний ще не вичерпний перелік прикладів з list
операцій і дійсно вони є поточно. Сподіваючись отримати відповідь щодо obj in a_list
мовної конструкції тут .
Нещодавно у мене був такий випадок, коли мені потрібно було додавати до списку в один потік, прокручувати елементи та перевіряти, чи готовий елемент, у моєму випадку це AsyncResult і видаляти його зі списку лише в тому випадку, якщо він готовий. Я не міг знайти жодних прикладів, які наочно продемонстрували мою проблему. Ось приклад демонстрації додавання до списку в одному потоці безперервного та видалення з того самого списку в іншому потоці безперервно. Недосконала версія легко працює на менших числах, але зберігає числа досить великими і виконує кілька разів, і ви побачите помилку
ВИМОГА версія
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 = []
lock = threading.RLock()
def add():
with lock:
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
with lock:
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)
Вихідні дані
[] # Empty list
Висновок
Як було сказано в попередніх відповідях, хоча акт додавання або вискакування елементів із самого списку безпечний для потоків, те, що не є безпечним для потоків, - це коли ви додаєте в одну нитку, а іншу -
with r:
), а не чітко зателефонувати r.acquire()
таr.release()