Подтвердить что ты не робот

Когда и как использовать Python RLock

Чтение через документы Python я встретил RLock.

Может кто-нибудь объяснить мне (с примерами) сценарий, в котором RLock будет предпочтительнее Lock?

С особой ссылкой на:

  • RLock "уровень рекурсии". Как это полезно?
  • Нить "собственность" объекта RLock
  • Производительность?
4b9b3361

Ответ 1

Это один из примеров, где я вижу использование:

Полезно, когда

  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()
    
  2. для рекурсии более очевидно:

    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 блокировка для разблокировки.

  • Производительность

Я не думаю, что есть разница в производительности, довольно концептуальная.