1. Lock (блокировка)

1. Lock (блокировка)


Синхронизация в Python: Lock, Semaphore и Queue

В многопоточных приложениях одновременный доступ к общим ресурсам может привести к состоянию гонки (race condition), повреждению данных или недетерминированному поведению. Для решения этих проблем в Python предоставляются механизмы синхронизации: Lock, Semaphore и Queue. Рассмотрим каждый из них.


1. Lock (блокировка)

Lock — базовый примитив синхронизации, который позволяет обеспечить эксклюзивный доступ к ресурсу. Только один поток может захватить блокировку, остальные ждут ее освобождения.

Пример проблемы без Lock

import threading

counter = 0

def increment():
    global counter
    for _ in range(100000):
        counter += 1

threads = []
for _ in range(5):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print("Counter:", counter)  # Результат может быть меньше 500000 из-за гонки данных.

Использование Lock

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        with lock:  # Автоматическое захват и освобождение
            counter += 1

threads = []
for _ in range(5):
    t = threading.Thread(target=increment)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print("Counter:", counter)  # Всегда 500000

Когда использовать: Для защиты критических участков кода, где важен эксклюзивный доступ (например, изменение общей переменной).


2. Semaphore (семафор)

Semaphore — это счетчик, ограничивающий количество потоков, которые могут одновременно получить доступ к ресурсу. Если семафор инициализирован значением N, то одновременно работать с ресурсом могут N потоков.

Пример использования

import threading
import time

semaphore = threading.Semaphore(3)  # Одновременно могут работать 3 потока

def access_resource(thread_id):
    with semaphore:
        print(f"Поток {thread_id} получил доступ")
        time.sleep(2)
    print(f"Поток {thread_id} освободил доступ")

threads = []
for i in range(10):
    t = threading.Thread(target=access_resource, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Результат: Первые три потока получают доступ сразу, остальные ждут освобождения семафора.

Когда использовать: Для ограничения доступа к ресурсам с ограниченной пропускной способностью (например, подключения к базе данных).


3. Queue (очередь)

Queue — потокобезопасная структура данных для обмена сообщениями между потоками. Она автоматически управляет блокировками, что упрощает реализацию шаблонов типа «Производитель-Потребитель».

Пример Producer-Consumer

import threading
import queue
import time

q = queue.Queue(maxsize=5)  # Очередь максимум на 5 элементов

def producer():
    for i in range(10):
        q.put(i)
        print(f"Произведено: {i}")
        time.sleep(0.5)

def consumer():
    while True:
        item = q.get()
        if item is None:  # Сигнал завершения
            break
        print(f"Потреблено: {item}")
        q.task_done()

# Запуск потоков
prod_thread = threading.Thread(target=producer)
cons_thread = threading.Thread(target=consumer)

prod_thread.start()
cons_thread.start()

prod_thread.join()
q.put(None)  # Отправка сигнала завершения потребителю
cons_thread.join()

Особенности:

  • Методы put() и get() блокируют поток, если очередь заполнена или пуста.
  • task_done() и join() используются для отслеживания завершения задач.

Когда использовать: Для безопасной передачи данных между потоками (например, обработка задач в фоне).


Сравнение и рекомендации

ИнструментНазначениеПример использования
LockЭксклюзивный доступ к ресурсуИзменение общей переменной
SemaphoreОграничение доступа N потоковПул подключений к базе данных
QueueБезопасный обмен даннымиМногопоточная обработка задач

Заключение

  • Lock защищает критические участки кода.
  • Semaphore ограничивает количество одновременных операций.
  • Queue упрощает обмен данными между потоками.

Выбор инструмента зависит от задачи: используйте Lock для синхронизации, Semaphore для ограничения доступа и Queue для передачи данных. Все эти механизмы помогают избежать ошибок в многопоточных приложениях.