Что такое примитивы синхронизации в программировании?

Ответ

Примитивы синхронизации — это базовые механизмы, используемые в многопоточном или многопроцессном программировании для координации доступа к общим ресурсам и предотвращения проблем, таких как состояния гонки (race conditions) и взаимоблокировки (deadlocks).

В Python модуль threading предоставляет следующие основные примитивы:

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

    • Назначение: Самый простой примитив для обеспечения взаимного исключения. Позволяет только одному потоку одновременно выполнять критическую секцию кода.
    • Пример:

      from threading import Lock
      
      shared_data = 0
      lock = Lock()
      
      def increment():
          global shared_data
          with lock: # Захватывает блокировку, гарантируя эксклюзивный доступ
              shared_data += 1
  2. RLock (Реентерабельная блокировка)

    • Назначение: Позволяет одному и тому же потоку захватывать блокировку несколько раз без возникновения взаимоблокировки. Это полезно, когда функция, уже владеющая блокировкой, вызывает другую функцию, которая также пытается захватить ту же блокировку.
    • Пример:

      from threading import RLock
      
      r_lock = RLock()
      
      def func1():
          with r_lock: # Первый захват
              print("Func1 acquired lock")
              func2()
      
      def func2():
          with r_lock: # Второй захват тем же потоком
              print("Func2 acquired lock")
  3. Semaphore (Семафор)

    • Назначение: Ограничивает количество потоков, которые могут одновременно получить доступ к ресурсу или выполнить определенную секцию кода. Используется для управления пулом ресурсов.
    • Пример:

      from threading import Semaphore
      
      # Разрешает до 3 потоков одновременно
      sem = Semaphore(3)
      
      def worker(name):
          import time
          with sem: # Захватывает семафор (уменьшает счетчик)
              print(f"Поток {name} работает с ресурсом...")
              # Имитация работы
              time.sleep(1)
              print(f"Поток {name} завершил работу.")
          # Семафор освобождается при выходе из 'with' (увеличивает счетчик)
  4. Event (Событие)

    • Назначение: Простой механизм для сигнализации между потоками. Один поток ждет, пока событие не будет установлено, а другой поток устанавливает его.
    • Пример:

      from threading import Event
      
      event = Event()
      
      def waiter():
          print("Ожидающий поток: жду события...")
          event.wait() # Блокируется, пока событие не будет установлено
          print("Ожидающий поток: событие получено!")
      
      def setter():
          import time
          time.sleep(2)
          print("Устанавливающий поток: устанавливаю событие.")
          event.set() # Разблокирует все ожидающие потоки
  5. Condition (Условие)

    • Назначение: Более сложный механизм, позволяющий потокам ждать выполнения определенного условия и быть уведомленными, когда это условие становится истинным. Часто используется для реализации паттерна "производитель-потребитель".
    • Пример:

      from threading import Condition
      
      items = []
      condition = Condition()
      
      def producer():
          with condition: # Захватывает связанный Lock
              items.append("item")
              print("Производитель: добавил элемент.")
              condition.notify() # Уведомляет один ожидающий поток
      
      def consumer():
          with condition: # Захватывает связанный Lock
              while not items: # Ждем, пока условие не станет истинным
                  print("Потребитель: жду элементы...")
                  condition.wait() # Освобождает Lock и ждет уведомления
              item = items.pop(0)
              print(f"Потребитель: обработал {item}.")

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