Безопасный обмен данными
Используйте очереди и блокировки
«Безопасный обмен данными» — бесплатный урок Python Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Python Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Python Academy содержит 4 уроков всего.
Проблема состояния гонки
Когда несколько потоков изменяют одни и те же данные, их операции могут перемешаться и повредить результат. Это называется состоянием гонки. Для безопасного обмена данными нужны средства синхронизации.
import threading
counter = 0
def bump():
global counter
for _ in range(100000):
counter += 1
ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter (may be < 200000):', counter)На помощь приходит Lock
threading.Lock гарантирует, что одновременно в критическую секцию войдёт только один поток. Получите блокировку с помощью блока with, охватывающего обновление общих данных.
import threading
counter = 0
lock = threading.Lock()
def bump():
global counter
for _ in range(100000):
with lock:
counter += 1
ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter:', counter)acquire и release
Блокировкой можно управлять вручную с помощью acquire() и release(), но форма с with безопаснее, поскольку освобождает блокировку даже при возникновении ошибки.
import threading
lock = threading.Lock()
lock.acquire()
try:
print('inside critical section')
finally:
lock.release()
print('released')Очереди, безопасные для потоков
Класс queue.Queue уже безопасен для потоков. Потоки могут вызывать put и get без явных блокировок, поэтому это предпочтительный способ передачи данных.
import queue, threading
q = queue.Queue()
def producer():
for i in range(3):
q.put(i)
t = threading.Thread(target=producer)
t.start()
t.join()
while not q.empty():
print('got', q.get())producer и consumer
Классический шаблон: производители помещают работу в очередь с помощью put, а потребители получают её через get и обрабатывают. Очередь самостоятельно управляет всеми блокировками.
import queue, threading
q = queue.Queue()
results = []
def consumer():
while True:
item = q.get()
if item is None:
break
results.append(item * 10)
q.task_done()
t = threading.Thread(target=consumer)
t.start()
for i in range(3):
q.put(i)
q.put(None)
t.join()
print(results)join и task_done
q.join() блокирует выполнение, пока для каждого элемента, переданного через put, не будет выполнен соответствующий вызов task_done(). Благодаря этому главный поток может дождаться завершения всей работы.
import queue, threading
q = queue.Queue()
def worker():
while True:
item = q.get()
q.task_done()
threading.Thread(target=worker, daemon=True).start()
for i in range(5):
q.put(i)
q.join()
print('all items processed')RLock для повторного входа
Обычная блокировка Lock приводит к взаимной блокировке, если тот же поток пытается получить её дважды. threading.RLock (повторно входящая блокировка) это разрешает, подсчитывая количество захватов.
import threading
rlock = threading.RLock()
def outer():
with rlock:
inner()
def inner():
with rlock:
print('re-entered safely')
outer()Event для передачи сигналов
threading.Event позволяет одному потоку передавать сигнал другим. Потоки вызывают wait(), пока другой поток не вызовет set().
import threading
start = threading.Event()
def worker():
start.wait()
print('go!')
t = threading.Thread(target=worker)
t.start()
print('setting event')
start.set()
t.join()Обмен данными между процессами
Для обмена между процессами вместо этого используйте multiprocessing.Queue. Она сериализует объекты при передаче между отдельными пространствами памяти.
import multiprocessing as mp
def producer(q):
for i in range(3):
q.put(i * i)
if __name__ == '__main__':
q = mp.Queue()
p = mp.Process(target=producer, args=(q,))
p.start()
p.join()
while not q.empty():
print('received', q.get())Как избежать взаимной блокировки
Взаимная блокировка возникает, когда потоки бесконечно ждут блокировки друг друга. Избегайте этого: всегда получайте несколько блокировок в одном и том же порядке во всех участках программы.
import threading
lock_a = threading.Lock()
lock_b = threading.Lock()
def safe():
with lock_a:
with lock_b:
print('acquired in consistent order')
safe()Предпочитайте очереди блокировкам
Если возможно, предпочитайте передачу сообщений через очереди общему изменяемому состоянию, защищённому блокировками. Так проще рассуждать о работе программы и гораздо меньше вероятность появления ошибок.
import queue
q = queue.Queue()
for word in ['safe', 'simple', 'clear']:
q.put(word)
while not q.empty():
print(q.get())Быстрая проверка
Проверьте, насколько хорошо Вы поняли безопасный обмен данными.
Итоги
Вы научились безопасно обмениваться данными:
- Состояния гонки повреждают незащищённые общие данные.
LockиRLockзащищают критические секции.queue.Queueбезопасна для потоков; между процессами используйтеmultiprocessing.Queue.Eventпередаёт сигналы между потоками; одинаковый порядок получения блокировок предотвращает взаимную блокировку.
Следующий курс: запуск внешних программ с помощью подпроцессов.
Часто задаваемые вопросы
Урок «Безопасный обмен данными» бесплатный?
Да — полный текст урока «Безопасный обмен данными» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Python Academy, подпишись на CoddyKit PRO. Курс Python Academy содержит 4 уроков всего.
Чему я научусь в уроке «Безопасный обмен данными»?
Используйте очереди и блокировки Ты практикуешь Python Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Python Academy?
Предыдущий опыт не требуется. Python Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Безопасный обмен данными»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Python Academy?
Да. Каждый урок Python Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Потоки и GIL
- ThreadPoolExecutor
- Основы multiprocessing
- Безопасный обмен данными