【问题标题】:non-blocking lock with 'with' statement带有'with'语句的非阻塞锁
【发布时间】:2015-07-19 12:59:40
【问题描述】:

据我所知,如果lock 已经被另一个线程获取,下面的代码将被阻塞。

似乎lock.acquire(0)可以实现非阻塞,但是我必须使用try-finally块而不是with块。

lock = threading.Lock()

def func():
 with lock:
  # do something...

有没有办法实现非阻塞锁获取?

【问题讨论】:

    标签: python multithreading locking


    【解决方案1】:
    @contextmanager
    def nonblocking(lock):
        locked = lock.acquire(False)
        try:
            yield locked
        finally:
            if locked:
                lock.release()
    
    lock = threading.Lock()
    with nonblocking(lock) as locked:
        if locked:
            do_stuff()
    

    【讨论】:

    • 欢迎来到 Stack Overflow!虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。不鼓励仅使用代码回答。
    【解决方案2】:

    有没有办法实现非阻塞获取锁?

    是的。如果无法立即获得锁,只需引发异常。比如:

    @contextlib.contextmanager
    def non_blocking_lock(lock=threading.Lock()):
        if not lock.acquire(blocking=False):
            raise WouldBlockError
        try:
            yield lock
        finally:
            lock.release()
    

    用法:

    with non_blocking_lock():
        # run with the lock acquired
    

    【讨论】:

    • 感谢您的回答。无论如何我必须使用lock.acquire 函数。
    • 我不明白默认参数的意义。锁将在上下文管理器中创建,并将被提供给已经锁定的用户——但当然没有其他线程知道这个锁。这在什么情况下有用?
    • @max:这不是默认参数在 Python 中的工作方式。了解"Least Astonishment" and the Mutable Default Argument 可能会有所帮助。
    • @J.F.Sebastian 啊对!我知道可变的默认参数是如何工作的,但我没有意识到这个功能可以用于善而不是恶:) 很酷,所以你得到了这个有效的全局锁实例,被上下文管理器定义“隐藏”,你只需使用这个没有参数的上下文管理器,就可以在任何地方隐式地依赖它。话虽如此,这似乎有点危险,因为参数的意外遗漏会导致非常微妙的错误,并且非常难以检测。
    • @max 如果你想要自己的锁;你通过你自己的锁。否则,您不应将任何参数传递给上下文管理器。是的,在多线程代码中很容易引入错误。
    【解决方案3】:

    如果您需要一个以非阻塞方式获取锁的上下文管理器,但仍会重试直到最终可以获取锁,您可以这样做:

    @contextlib.contextmanager
    def non_blocking_lock(lock : threading.Lock):
        # Waits as long as the lock can not be acquired, but releases the GIL in the meanwhile
        while not lock.acquire(blocking=False):
            pass
    
        try:
            yield   # Lock has been successfully acquired
        finally:
            lock.release()
    

    它可以像普通的锁上下文管理器一样使用:

    class TestClass:
        def __init__(self):
             self._lock = threading.Lock()
        
        def method(self):
             with non_blocking_lock(self._lock):
             # do something that should be only done from one thread at once
    

    ... 不同的是,锁是非阻塞的,并且在锁被释放之前不会持有 GIL。我用它来修复一些死锁。

    与其他解决方案的不同之处在于,代码最终会被执行,并且上下文管理器不会在无法获取锁时简单地返回 false 或抛出异常。

    如果您发现此解决方案有任何警告,请纠正我。

    【讨论】:

      【解决方案4】:

      锁的全部意义在于确保程序的某些部分一次只能由一个线程或进程执行。这是通过阻止任何线程/进程尝试获取锁而其他东西持有它来实现的。

      如果你不想获取锁来阻塞,你为什么首先使用锁?大概是为了让您在等待时可以做点别的事情?

      要尝试获取锁l 而不阻塞,请调用l.acquire(blocking=False)。如果没有获得锁,这将立即返回False。如果锁获得,它会返回True,你将继续持有锁,直到你调用它的release()方法。

      然而,这种形式对于with 语句并不是特别有用。通常您希望受控代码(with 之后的缩进套件)仅在获得锁时运行。不去查询是否有,采取两种替代的行动。

      【讨论】:

      • 谢谢。我必须使用lock.acquire()。 :D.
      • 正是特殊情况需要问题中要求的解决方案。 with 构造允许干净地处理锁定获取成功的情况。
      【解决方案5】:

      如果你想使用带有非阻塞锁的 with 语句,你也可以先检查它是否被锁定。如果是,那么您就不要进入 with 块。例如:

      lock = threading.Lock()
      
      def func():
          if not lock.locked():
              with lock:
                  # do something...
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-02-05
        • 1970-01-01
        • 1970-01-01
        • 2020-05-18
        • 1970-01-01
        • 2023-04-01
        相关资源
        最近更新 更多