2013-05-15 13 views
30

Czytając dokumentację w języku Python natknąłem się na RLock.Kiedy i jak używać Pythona RLock

Czy ktoś może mi wyjaśnić (z przykładem) scenariusz, w którym RLock byłby bardziej preferowany niż Lock?

Ze szczególnym odniesieniem do:

  • RLock „S«poziom rekurencji». Jak to jest przydatne?
  • Własność wątków "własności" obiektu RLock
  • Wydajność?
+2

Zobacz również http://stackoverflow.com/questions/8720783/recursive-locks i http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex –

Odpowiedz

33

Jest to jeden z przykładów, gdzie widzę zastosowanie:

Przydatne gdy

  1. chcesz mieć threadsave dostępu spoza klasy i używać tych samych metod od wewnątrz klasy :

    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 threadsave! 
         with self.lock: 
          self.changeA() # a usual lock would block in here 
          self.changeB() 
    
  2. dla rekursji bardziej oczywiste:

    lock = threading.RLock() 
    def a(...): 
        with lock: 
    
         a(...) # somewhere inside 
    

    inne wątki muszą czekać aż do pierwszego wezwania a wykończeń = własności wątek.

Wydajność

Zazwyczaj zaczynam programowanie z zamka i gdy przypadek 1 lub 2 wystąpić, przełączyć do RLock. Until Python 3.2 RLock powinien być nieco wolniejszy z powodu dodatkowego kodu. Wykorzystuje blokady:

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() 

Własność Temat

w danym wątku można nabyć RLock tak często, jak chcesz. Inne wątki muszą poczekać, aż wątki ponownie zwrócą zasób.

To różni się od Lock, co implikuje "funkcję wywołania funkcji" (nazwałbym to w ten sposób): Inne wywołanie funkcji musi czekać, aż zasób zostanie zwolniony przez ostatnią funkcję blokowania, nawet jeśli jest w tym samym thread = nawet jeśli jest wywoływana przez inną funkcję.

Kiedy używać blokady zamiast RLock

Po dokonaniu połączenia z zewnątrz zasobów, których nie można kontrolować.

Poniższy kod zawiera dwie zmienne: A i B, a RLock są używane aby upewnić się, 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 

W changeAandB2 Blokada byłoby właściwej decyzji, chociaż nie blokuje.Lub można go poprawić za pomocą błędów przy użyciu RLock._is_owned(). Funkcje takie jak changeAandB2 może wystąpić podczas wdrożyliśmy wzorca obserwator lub wydawca abonenta i dodać blokowania później.

+0

Świetna odpowiedź! Czy "_is_owned" jest udokumentowane w dowolnym miejscu? Spodziewałem się pewnego rodzaju atrybutu lub metody 'owner_name', ale nie mogę znaleźć niczego udokumentowanego. – Awalias

+0

' _is_owned() 'zaczyna się od znaku podkreślenia i dlatego nie oczekuje się, że go użyje. (Zwraca identyfikator bieżnika 'sys._current_treads()'.) 'Wymagają (fałsz)' jest nonblocking i wystarczy w większości przypadków. – User

+2

Dla przyszłych czytelników, w [Python 3.2 i wyżej, koszt wydajności 'RLock' jest w zasadzie zero] (https://docs.python.org/3/whatsnew/3.2.html#optimizations), ponieważ' RLock' jest zaimplementowane w C podobnie jak 'Lock'; wcześniej był wolniejszy, ponieważ wykonał wiele kodu Pythona, aby zawinąć 'Lock', ale w 3.2+, nie ma żadnych kosztów (poza tym, że 'Lock' jest odblokowywany z innych wątków, gdzie' RLock' jest tylko możliwy do odblokowania przez właściciela, co jest znaczącym rozróżnieniem w takich rzeczach, jak implementacja 'Condition', która używa odblokowań z wieloma wątkami powiadomić kelnerów). – ShadowRanger

3
  • poziom rekurencji
  • własność

Prymitywny blokady (Lock) jest synchronizacja prymitywne, że nie jest w posiadaniu danego wątku, gdy zablokowana.

Dla powtarzalnej blokadę (RLock) w stanie zablokowanym, niektóre nici posiada blokadę; w stanie odblokowanym żaden wątek nie jest jego właścicielem. Wywołany jeśli ten wątek już posiada blokadę, zwiększamy poziom rekurencji po drugim, i wrócić natychmiast. jeśli wątek nie jest właścicielem blokady Czeka, aż właściciel zwolni blokadę. Zwolnij blokadę, zmniejszając poziom rekursji. Jeśli po zmniejszeniu wynosi zero, zresetuj blokadę, aby odblokować.

  • Wydajność

Nie sądzę, jest jakaś różnica wydajności raczej koncepcyjny jeden.

2

Oto kolejny przypadek użycia dla RLock. Załóżmy, że masz interfejs sieciowy obsługujący współbieżny dostęp, ale musisz zarządzać niektórymi rodzajami dostępu do zasobów zewnętrznych. Na przykład, musisz zachować spójność między obiektami w pamięci i obiektami w bazie danych, a masz klasę menedżerów, która kontroluje dostęp do bazy danych, z metodami, które musisz zapewnić, aby były wywoływane w określonej kolejności i nigdy jednocześnie.

Co można zrobić, to stworzyć RLock i wątek stróża, który kontroluje dostęp do RLock poprzez ciągłe nabywanie go i zwalniając tylko wtedy sygnalizowana. Następnie upewniasz się, że wszystkie metody kontroli dostępu są wykonywane w celu uzyskania blokady przed uruchomieniem. Coś takiego:

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 

ten sposób, masz zapewnione następujące rzeczy:

  1. Po nitki nabywa blokadę zasobów, może to wywołać chronionych metod menedżera zasobów za darmo, bo RLock jest rekurencyjny
  2. Gdy wątek uzyska blokadę zasobów metodą master w interfejsie sieciowym, dostęp do metod chronionych w menedżerze zostanie zablokowany do innych wątków
  3. Dostęp do metod chronionych w menedżerze można uzyskać tylko najpierw zwracając się do opiekuna.
Powiązane problemy