Короткий ответ
RLock (reentrant lock) позволяет одному и тому же потоку захватывать лок повторно без самоблокировки (счётчик захватов), тогда как обычный Lock заблокирует поток при попытке повторного захвата уже удержанного им же лока, что приведёт к дедлоку.
Как это работает подробнее
RLock хранит, какой поток владеет локом, и счётчик вложенных acquire(); release() нужно вызывать столько же раз, сколько было acquire(), прежде чем лок станет доступен другим потокам. Обычный Lock не знает про владельца — повторный вызов acquire() из того же потока блокируется.
Пример кода
example-264.py
import threading
class Counter:
def __init__(self):
self.lock = threading.Lock() # замена на threading.RLock() устранит дедлок
self.value = 0
def increment(self):
with self.lock:
self.value += 1
self.increment_twice_internal() # повторный захват того же lock
def increment_twice_internal(self):
with self.lock: # с обычным Lock здесь дедлок в том же потоке
self.value += 1
c = Counter()
# c.increment() зависнет с threading.Lock(), но сработает с threading.RLock()Что использовать на практике
- Практика.
RLockнужен, когда метод, уже держащий лок, вызывает другой метод того же объекта, который тоже пытается захватить тот же лок (рекурсивные или вложенные вызовы). Если такой вложенности нет, обычныйLockобычно достаточен и немного быстрее.
Подводные камни
- Риски. Используют
Lockтам, где есть рекурсивные захваты того же лока одним потоком, получая неочевидный дедлок; используютRLock«на всякий случай» везде, скрывая архитектурную проблему вложенных блокировок вместо её устранения.