En lisant les documents Python que j'ai rencontrés RLock
.
Quelqu'un peut-il m'expliquer (avec un exemple) un scénario dans lequel RLock
serait préféré à Lock
?
Avec une référence particulière à:
RLock
. Comment est-ce utile?RLock
Ceci est un exemple où je vois l'utilisation:
Utile lorsque
vous voulez avoir un accès thread-safe depuis l'extérieur de la classe et utiliser les mêmes méthodes depuis l'intérieur de la classe:
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()
pour une récursion plus évidente:
lock = threading.RLock()
def a(...):
with lock:
a(...) # somewhere inside
les autres threads doivent attendre la fin du premier appel de a
= propriété du thread.
Performances
Habituellement, je commence la programmation avec le verrou et lorsque le cas 1 ou 2 se produit, je passe à un RLock. Jusqu'à Python 3.2 le RLock devrait être un peu plus lent à cause du code supplémentaire. Il utilise 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()
Propriété du fil
dans le thread donné, vous pouvez acquérir un RLock
aussi souvent que vous le souhaitez. Les autres threads doivent attendre jusqu'à ce que ce thread libère à nouveau la ressource.
Ceci est différent du Lock
qui implique la "propriété de l'appel de fonction" (je l'appellerais de cette façon): Un autre appel de fonction doit attendre que la ressource soit libérée par la dernière fonction de blocage même si elle se trouve dans le même thread = même s'il est appelé par l'autre fonction.
Quand utiliser Lock au lieu de RLock
Lorsque vous effectuez un appel vers l'extérieur de la ressource que vous ne pouvez pas contrôler.
Le code ci-dessous a deux variables: a et b et le RLock doit être utilisé pour s'assurer que 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
Dans changeAandB2
, Le verrou serait le bon choix bien qu'il se bloque. Ou on peut l'améliorer avec des erreurs en utilisant RLock._is_owned()
. Des fonctions telles que changeAandB2
Peuvent se produire lorsque vous avez implémenté un modèle Observer ou un éditeur-abonné et ajoutez un verrouillage par la suite.
Voici un autre cas d'utilisation pour RLock. Supposons que vous disposez d'une interface utilisateur Web prenant en charge l'accès simultané, mais que vous devez gérer certains types d'accès à une ressource externe. Par exemple, vous devez maintenir la cohérence entre les objets en mémoire et les objets dans une base de données, et vous disposez d'une classe de gestionnaire qui contrôle l'accès à la base de données, avec des méthodes dont vous devez vous assurer d'être appelé dans un ordre spécifique, et jamais simultanément.
Ce que vous pouvez faire est de créer un RLock et un thread gardien qui contrôle l'accès au RLock en l'acquérant constamment et en le libérant uniquement lorsque cela est signalé. Ensuite, vous vous assurez que toutes les méthodes dont vous avez besoin pour contrôler l'accès sont faites pour obtenir le verrou avant leur exécution. Quelque chose comme ça:
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
De cette façon, vous êtes assuré des choses suivantes:
Un verrou primitif (Lock) est une primitive de synchronisation qui n'appartient pas à un thread particulier lorsqu'il est verrouillé.
Pour le verrou répétable (RLock) À l'état verrouillé, certains threads possèdent le verrou; à l'état déverrouillé, aucun thread ne le possède. Lorsqu'il est appelé si ce thread possède déjà le verrou, incrémentez le niveau de récursivité de un et revenez immédiatement. si le thread ne possède pas le verrou Il attend que le propriétaire déverrouille le verrou. Relâchez un verrou, décrémentant le niveau de récursivité. Si après la décrémentation, il est nul, réinitialisez le verrou sur déverrouillé.
Je ne pense pas qu'il y ait une différence de performance plutôt conceptuelle.