0Pricing
Python Academy · Урок

Безопасный обмен данными

Используйте очереди и блокировки

«Безопасный обмен данными» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Потоки и GIL
  2. ThreadPoolExecutor
  3. Основы multiprocessing
  4. Безопасный обмен данными
← Назад к Python Academy