Как можно синхронизировать потоки с помощью `threading.Lock()`?

Используйте threading.Lock() для создания объекта блокировки. Перед критической секцией кода, вызывайте метод lock.acquire(), чтобы захватить блокировку. После выполнения критической секции, вызывайте lock.release(), чтобы освободить блокировку. Только один поток может владеть блокировкой в один момент времени, что предотвращает гонки данных и обеспечивает синхронизацию. Рекомендуется использовать конструкцию try...finally или with lock: чтобы гарантировать освобождение блокировки даже при возникновении исключений.

Синхронизация потоков с помощью threading.Lock() в Python позволяет избежать состояния гонки (race condition) и обеспечить корректную работу при обращении нескольких потоков к общим ресурсам. threading.Lock() реализует механизм мьютекса (mutual exclusion), предоставляя только одному потоку доступ к защищенному коду в любой момент времени.

Основной принцип использования заключается в следующем:

  1. Создание объекта Lock: Сначала создается экземпляр класса threading.Lock(), который будет использоваться для защиты критической секции кода.
  2. Захват блокировки (Acquire): Перед входом в критическую секцию, поток должен вызвать метод lock.acquire(). Если блокировка свободна, поток захватывает ее и продолжает выполнение. Если блокировка уже захвачена другим потоком, текущий поток будет заблокирован (ожидать), пока блокировка не будет освобождена.
  3. Критическая секция: Код, требующий синхронизации (например, изменение общих данных), помещается внутри блока, защищенного блокировкой.
  4. Освобождение блокировки (Release): После выполнения критической секции, поток должен вызвать метод lock.release(), чтобы освободить блокировку. Это позволяет другим потокам, ожидающим блокировку, продолжить выполнение.

Пример кода:


import threading
import time

# Общий ресурс (например, счетчик)
shared_counter = 0
# Создаем блокировку
lock = threading.Lock()

def increment_counter(thread_id):
  global shared_counter
  for _ in range(100000):
    # Захватываем блокировку
    lock.acquire()
    try:
      shared_counter += 1
      # Критическая секция: изменяем общий ресурс
    finally:
      # Освобождаем блокировку (важно делать в блоке finally, чтобы гарантировать освобождение даже при исключениях)
      lock.release()
  print(f"Поток {thread_id} завершил работу")

# Создаем и запускаем потоки
threads = []
for i in range(2):
  thread = threading.Thread(target=increment_counter, args=(i,))
  threads.append(thread)
  thread.start()

# Ожидаем завершения всех потоков
for thread in threads:
  thread.join()

print(f"Финальное значение счетчика: {shared_counter}")
  

Важные моменты:

  • Всегда используйте блок try...finally для освобождения блокировки, чтобы гарантировать ее освобождение даже в случае возникновения исключений внутри критической секции. Не освобождение блокировки может привести к дедлоку (deadlock).
  • Убедитесь, что все потоки, обращающиеся к общему ресурсу, используют одну и ту же блокировку.
  • Использование блокировок может снизить производительность, так как потоки могут простаивать в ожидании освобождения блокировки. Не злоупотребляйте ими, синхронизируйте только действительно необходимые участки кода.
  • threading.Lock() является реентерабельным (reentrant). Это означает, что поток, уже владеющий блокировкой, может захватить ее повторно без блокировки, но он также должен освободить ее столько же раз, сколько раз он ее захватил. Для реентерабельных блокировок используйте threading.RLock().

0