Короткий ответ
threading.Lock — примитив синхронизации, гарантирующий, что в охраняемый блок кода в любой момент может войти только один поток, что превращает составную операцию в фактически атомарную относительно других потоков.
Как это работает подробнее
Lock.acquire() блокирует поток, если лок уже захвачен другим потоком, и пропускает его, если лок свободен; release() освобождает лок. Конструкция with lock: эквивалентна acquire()/release() с гарантированным release() даже при исключении.
Пример кода
example-255.py
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100_000):
with lock:
counter += 1 # теперь безопасно: только один поток внутри блока одновременно
threads = [threading.Thread(target=increment) for _ in range(4)]
[t.start() for t in threads]
[t.join() for t in threads]
print(counter) # теперь стабильно 400000 при каждом запускеЧто использовать на практике
- Практика. Лок оборачивают вокруг минимально необходимого участка кода, читающего и изменяющего общие данные, чтобы не создавать лишних задержек для остальных потоков и не удерживать лок во время долгих операций.
Подводные камни
- Риски. Оборачивают локом слишком большой участок кода, из-за чего многопоточность теряет смысл; забывают освободить лок вручную без
with, что при исключении приводит к «зависшему» локу и дедлоку остальных потоков.