Python threading.Lock: защитить общий счётчик

Категория: Python · Язык: Python · Обновлено:

threading.Lock защищает участок кода, который несколько потоков не должны выполнять одновременно. GIL не делает операции вроде counter += 1 атомарными: поток может быть вытеснен между чтением и записью. Блокировка вокруг общего состояния делает результат предсказуемым.

Синхронизировать инкремент

Python
import threading

counter = 0
lock = threading.Lock()

def worker():
    global counter
    for _ in range(1000):
        with lock:
            counter += 1

threads = [threading.Thread(target=worker) for _ in range(4)]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

print(counter)  # 4000

Контекстный менеджер with lock захватывает блокировку и гарантированно освобождает её даже при исключении. Без него четыре потока могли бы прочитать одно и то же значение и потерять часть инкрементов. Чем короче критическая секция, тем меньше потоков ждут друг друга.

Понять повторный вход

Python
import threading

lock = threading.RLock()
with lock:
    with lock:
        print("повторный вход")  # повторный вход

RLock разрешает тому же потоку захватить блокировку повторно; освобождать её нужно столько же раз. Обычный Lock в такой ситуации зависнет навсегда. RLock полезен для рекурсивных функций и методов, которые вызывают друг друга, но не заменяет продуманную структуру блокировок.

Не удерживайте блокировку во время сетевого запроса, чтения большого файла или sleep: другие потоки будут простаивать. Не захватывайте несколько блокировок в разном порядке — это классическая причина взаимной блокировки. Если нужен таймаут, используйте lock.acquire(timeout=1) и обрабатывайте False.

Для процессов threading.Lock не работает: нужен multiprocessing.Lock или внешняя блокировка вроде файла/БД. Для потоковых данных лучше подходит queue.Queue, а для счётчика — itertools.count с осторожностью или отдельный сервис. Блокировка защищает только те операции, которые действительно выполняются под ней.

Документация: threading.Lock, RLock.