Когда и как использовать Python RLock
Чтение через документы Python я встретил RLock
.
Может кто-нибудь объяснить мне (с примерами) сценарий, в котором RLock
будет предпочтительнее Lock
?
С особой ссылкой на:
-
RLock
"уровень рекурсии". Как это полезно?
- Нить "собственность" объекта
RLock
- Производительность?
Ответы
Ответ 1
Это один из примеров, где я вижу использование:
Полезно, когда
вы хотите иметь потокобезопасный доступ извне класса и использовать те же методы изнутри класса:
class X:
def __init__(self):
self.a = 1
self.b = 2
self.lock = threading.RLock()
def changeA(self):
with self.lock:
self.a = self.a + 1
def changeB(self):
with self.lock:
self.b = self.b + self.a
def changeAandB(self):
# you can use chanceA and changeB thread-safe!
with self.lock:
self.changeA() # a usual lock would block at here
self.changeB()
для рекурсии более очевидно:
lock = threading.RLock()
def a(...):
with lock:
a(...) # somewhere inside
другие потоки должны дождаться окончания первого вызова a
= владение потоком.
Производительность
Обычно я начинаю программировать с помощью Lock, и когда происходит случай 1 или 2, я переключаюсь на RLock. До Python 3.2 RLock должен быть немного медленнее из-за дополнительного кода. Использует Lock:
Lock = _allocate_lock # line 98 threading.py
def RLock(*args, **kwargs):
return _RLock(*args, **kwargs)
class _RLock(_Verbose):
def __init__(self, verbose=None):
_Verbose.__init__(self, verbose)
self.__block = _allocate_lock()
Право собственности на нити
в рамках данного потока вы можете получить RLock
так часто, как вам нравится. Другие потоки должны ждать, пока этот поток снова не освободит ресурс.
Это отличается от Lock
, который подразумевает "владение вызовом функции" (я бы назвал это так): другой вызов функции должен ждать, пока ресурс не будет освобожден последней блокирующей функцией, даже если он находится в том же потоке = даже если он вызывается другой функцией.
Когда использовать Lock вместо RLock
Когда вы совершаете звонок на внешний ресурс, которым вы не можете управлять.
В приведенном ниже коде есть две переменные: a и b, и RLock должен использоваться, чтобы убедиться, что a == b * 2
import threading
a = 0
b = 0
lock = threading.RLock()
def changeAandB():
# this function works with an RLock and Lock
with lock:
global a, b
a += 1
b += 2
return a, b
def changeAandB2(callback):
# this function can return wrong results with RLock and can block with Lock
with lock:
global a, b
a += 1
callback() # this callback gets a wrong value when calling changeAandB2
b += 2
return a, b
В changeAandB2
замок будет правильным выбором, хотя он и блокирует. Или можно улучшить это с помощью ошибок, используя RLock._is_owned()
. Такие функции, как changeAandB2
, могут возникать, когда вы реализовали шаблон Observer или Publisher-Subscriber и добавили блокировку позже.
Ответ 2
Вот еще один вариант использования RLock. Предположим, у вас есть веб-интерфейс пользователя, который поддерживает одновременный доступ, но вам нужно управлять определенными видами доступа к внешнему ресурсу. Например, вы должны поддерживать согласованность между объектами в памяти и объектами в базе данных, и у вас есть класс менеджера, который контролирует доступ к базе данных, с помощью методов, которые должны быть вызваны в определенном порядке и никогда не выполняться одновременно.
Что вы можете сделать, так это создать RLock и поток хранителей, который контролирует доступ к RLock, постоянно приобретая его и освобождая только тогда, когда сигнализируется. Затем вы обеспечиваете, чтобы все методы, необходимые для контроля доступа, были сделаны для получения блокировки до их запуска. Что-то вроде этого:
def guardian_func():
while True:
WebFacingInterface.guardian_allow_access.clear()
ResourceManager.resource_lock.acquire()
WebFacingInterface.guardian_allow_access.wait()
ResourceManager.resource_lock.release()
class WebFacingInterface(object):
guardian_allow_access = Event()
resource_guardian = Thread(None, guardian_func, 'Guardian', [])
resource_manager = ResourceManager()
@classmethod
def resource_modifying_method(cls):
cls.guardian_allow_access.set()
cls.resource_manager.resource_lock.acquire()
cls.resource_manager.update_this()
cls.resource_manager.update_that()
cls.resource_manager.resource_lock.release()
class ResourceManager(object):
resource_lock = RLock()
def update_this(self):
if self.resource_lock.acquire(False):
try:
pass # do something
return True
finally:
self.resource_lock.release()
else:
return False
def update_that(self):
if self.resource_lock.acquire(False):
try:
pass # do something else
return True
finally:
self.resource_lock.release()
else:
return False
Таким образом, вы обеспечиваете следующие вещи:
- Когда поток получает блокировку ресурса, он может свободно обращаться к защищенным методам менеджера ресурсов, поскольку RLock рекурсивный
- Когда поток получает блокировку ресурсов через мастер-метод в интерфейсе, обращенном к сети, все доступ к защищенным методам в менеджере будет заблокирован для других потоков
- Защищенные методы в менеджере могут быть доступны только с первого обращения к опекуну.
Ответ 3
- уровень рекурсии
- собственности
Примитивная блокировка (Lock) - это примитив синхронизации, который не принадлежит определенному потоку при блокировке.
Для повторяемой блокировки (RLock) В заблокированном состоянии какой-то поток владеет блокировкой; в незаблокированном состоянии ни одна из них не владеет им.
При вызове, если этот поток уже владеет блокировкой, увеличивайте уровень рекурсии на единицу и немедленно возвращайтесь. если нить не принадлежит блокировке. Он ждет, пока блокировка владельца не будет заблокирована.
Отпустите блокировку, уменьшив уровень рекурсии. Если после декремента оно равно нулю, reset блокировка для разблокировки.
Я не думаю, что есть разница в производительности, довольно концептуальная.