2026, Jan 14 06:02

acquire_lock и release_lock в threading.Lock: почему это алиасы и что использовать

Python 3.13: недокументированные алиасы threading.Lock — acquire_lock и release_lock. Почему их не стоит вызывать и как безопасно работать с блокировками.

Недокументированные двойники в Lock Python: что на самом деле представляют собой acquire_lock и release_lock

Работа с threading.Lock в Python 3.13 может сбивать с толку, когда IDE показывают методы вроде acquire_lock, release_lock и даже locked_lock рядом с публичными acquire, release и locked. Возникает естественный вопрос: это разные ветки кода или просто альтернативные имена, и какие из них стоит вызывать в рабочем коде.

Код, из-за которого возникает вопрос

Допустим, вы видите или пишете код, который использует имена с суффиксом «_lock»:

import threading

mutex_gate = threading.Lock()

mutex_gate.acquire_lock()
try:
    # критическая секция, защищённая блокировкой
    payload = "work"
finally:
    mutex_gate.release_lock()

Это выполняется, но заставляет насторожиться: таких методов нет в публичной документации. Есть ли у них какие‑то особенности по сравнению с acquire и release?

Что происходит на самом деле

Это просто алиасы. В CPython acquire_lock указывает на ту же базовую функцию, что и acquire, а release_lock — на ту же реализацию, что и release. Судя по истории проекта, так уже около 15 лет. Имена существуют, но намеренно не документируются, то есть не входят в публичный контракт API и могут быть удалены в любой момент. В отличие от этого, acquire() документирован, а acquire_lock() — нет; уже один этот факт ясно показывает, какой метод ожидается использовать в реальном коде.

Некоторые IDE могут показывать дополнительные имена в сигнатурах или подсказках, что вводит в заблуждение. Также обратите внимание: в публичной части threading.py Lock представлен иначе; варианты с суффиксом «lock» — это внутренние алиасы, а не пользовательское API.

Как правильно писать код

Самый безопасный и удобный для сопровождения шаблон — использовать менеджер контекста. Он захватывает блокировку ровно в месте начала блока with и освобождает её сразу при выходе из блока, в том числе при исключениях. Никаких временных накладных расходов по сравнению с ручными вызовами; вы чётко ограничиваете строки, которые должны быть защищены.

import threading

sync_guard = threading.Lock()

with sync_guard:
    # защищаемый участок
    result = "work"

Если вы предпочитаете явные вызовы acquire/release, используйте только документированные имена и обязательно сочетайте их с try/finally, чтобы блокировка всегда освобождалась:

import threading

spin_lock = threading.Lock()

spin_lock.acquire()
try:
    # защищаемый участок
    outcome = "work"
finally:
    spin_lock.release()

Функционально этот вариант и форма с with эквивалентны по области действия и по времени. Просто with сложнее использовать неверно.

Почему это важно знать

Недокументированные API хрупки. Даже если сегодня всё работает, у них нет гарантий стабильности — они могут исчезнуть или измениться без предупреждения. Использование публичных методов acquire, release и locked — или ещё лучше, протокола менеджера контекста — держит ваш код в рамках официального контракта и избавляет от сюрпризов между версиями Python и окружениями.

Практический вывод

Если в инструментах попадаются acquire_lock, release_lock или locked_lock, считайте их внутренними алиасами и избегайте их. Предпочитайте with lock: ради ясности и безопасности при исключениях. Если нужен ручной контроль, вызывайте acquire() и release() внутри try/finally. Эта небольшая дисциплина делает конкурентный код предсказуемым и устойчивым, оставаясь в русле поддерживаемой документацией.