何时以及如何使用 Python 的 RLock

通过阅读 Python 文档,我偶然发现了 RLock

有人能给我解释一下(举个例子)在哪种情况下 RLock会比 Lock更好吗?

特别是:

  • RLock的“递归级别”。这有什么用?
  • RLock对象的线程“所有权”
  • 表演?
42171 次浏览

这是一个我认为有用的例子:

有用的时候

  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. for recursion more obvious:

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

    其他线程必须等到 a的第一个调用完成 = 线程所有权。

表演

通常,我开始使用 Lock 编程,当情况1或2发生时,我切换到 RLock。由于额外的代码,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()错误地增强它。当您实现了一个观察者模式或 Publisher-Subscriber 并在实现之后添加锁定时,可能会出现类似于 changeAandB2的函数。

  • 递归级递归级递归级递归级
  • 所有权

基元锁(Lock)是一个同步基元,锁定时不属于特定线程。

对于可重复锁(RLock)在锁定状态下,某个线程拥有该锁; 在解锁状态下,没有线程拥有该锁。 如果此线程已经拥有该锁,则在调用时将递归级别增加1,并立即返回。如果线程不拥有锁,它会等到所有者释放锁。 释放一个锁,递减递归级别。如果递减之后为零,重置锁为解锁。

  • 表演

我不认为存在性能上的差异,而是概念上的差异。

下面是 RLock 的另一个用例。假设您有一个面向 Web 的用户界面,支持并发访问,但是您需要管理对外部资源的某些类型的访问。例如,您必须保持内存中的对象和数据库中的对象之间的一致性,并且您有一个控制对数据库的访问的管理器类,您必须确保以特定的顺序调用这些方法,而且永远不要并发调用。

你所能做的就是创建一个 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

通过这种方式,你可以确保做到以下几点:

  1. 一旦线程获取了资源锁,它就可以自由地调用资源管理器的受保护方法,因为 RLock 是递归的
  2. 一旦线程通过面向 web 界面的主方法获得资源锁,管理器中所有对受保护方法的访问都将被其他线程阻塞
  3. 管理器中受保护的方法只能通过首先请求监护人来访问。