web-dev-qa-db-fra.com

Quand et comment utiliser Python RLock

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 à:

  • le "niveau de récursivité" de RLock. Comment est-ce utile?
  • Un thread "propriétaire" d'un objet RLock
  • Performance?
42
Awalias

Ceci est un exemple où je vois l'utilisation:

Utile lorsque

  1. 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()
    
  2. 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.

47
User

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:

  1. Une fois qu'un thread acquiert le verrou de ressource, il peut appeler librement les méthodes protégées du gestionnaire de ressources, car RLock est récursif
  2. Une fois que le thread acquiert le verrou de ressource via la méthode principale dans l'interface Web, tout accès aux méthodes protégées dans le gestionnaire sera bloqué vers d'autres threads.
  3. Les méthodes protégées dans le gestionnaire ne sont accessibles qu'en faisant d'abord appel au tuteur.
4
Corey Moncure
  • niveau de récursivité
  • la possession

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é.

  • Performance

Je ne pense pas qu'il y ait une différence de performance plutôt conceptuelle.

3
oleg