如何在Python中避免类属性初始化时的死锁,涉及锁、继承和线程?

1 投票
3 回答
1645 浏览
提问于 2025-04-18 03:35

我正在尝试实现线程安全的代码,但遇到了一些简单的问题。我搜索了一下,没有找到解决方案。

让我用抽象代码来描述这个问题:

import threading

class A(object):
  sharedLock = threading.Lock()
  shared = 0

  @classmethod
  def getIncremented(cls):
    with cls.sharedLock:
      cls.shared += 1
      return cls.shared

class B(A):
  pass

class C(A):
  @classmethod
  def getIncremented(cls):
    with cls.sharedLock:
      cls.shared += B.getIncremented()
      return cls.shared

我想定义一个类A,然后让它有很多子类,比如用于枚举或者懒加载变量——具体用途不重要。我已经完成了单线程版本,现在想更新为多线程。

这段代码应该能产生这样的结果:

id(A.sharedLock) 11694384
id(B.sharedLock) 11694384
id(C.sharedLock) 11694384

我的意思是,class A中的锁在class B中也会锁住,所以这不好,因为第一次进入class B时也会锁住class Aclass C。如果C使用B,就会导致死锁

我可以使用RLock,但这不是一个有效的编程模式,我不确定这样是否会导致更严重的死锁。

我该如何在类初始化时更改sharedLock的值,以便让id(A.sharedLock) != id(B.sharedLock),同样适用于AC,以及BC

我该如何在Python中通用地挂钩类的初始化,以便更改一些类变量?

这个问题并不复杂,但我不知道该怎么处理。

3 个回答

1

正如你所注意到的,如果把共享锁作为类的属性暴露出来,这些锁会被子类共享。

你可以通过在每个子类中重新定义锁来解决这个问题:

class B(A):
  sharedLock = threading.Lock()

你甚至可以使用 metaclass(元类)来实现这个,但我建议你不要这样做。看起来你可能是从错误的角度来考虑这个程序。

如果你将锁明确地分配给实例(而不是类),这个任务会简单很多。

class A(object):
    def __init__(self, lock):
        this.sharedLock= lock

my_lock= threading.Lock()
a= A(my_lock)

当然,这样做会遇到一个“问题”,就是你需要为每个实例明确地传递锁。这个问题通常可以通过使用工厂模式来解决,但在 Python 中,你可以简单地正确使用函数:

from functools import partial
A_with_mylock= partial(A, my_lock)
a2= A_with_mylock()
3

我想要继承父类的共享变量,但不想要共享父类的锁

这样做是不行的。这会导致“共享变量”的访问不安全。


sharedLock 用来保护 shared 变量。如果在递归调用中同一个 shared 变量可以被修改,那么你需要使用 RLock()。这里的 shared 是指在所有子类之间共享的。

看起来你想要一个独立的函数(或者静态方法),而不是类方法:

def getIncremented(_lock=Lock(), _shared=[0]):
    with _lock:
      _shared[0] += 1
      return _shared[0]

这样所有的类都会使用同一个 shared 变量(以及相应的 lock)。

如果你希望每个类都有自己的 shared 变量(这里的 shared 是指在这个特定类的实例之间共享),那么就不要使用 cls.shared,因为它可能会去查找父类来获取这个变量。

为了提示子类不应该直接使用某个变量,你可以使用私有变量的语法:

class A:
   __shared = 0
   __lock = Lock()

如果一个子类重写了一个使用 __shared 的方法,那么在代码中就不会意外地直接使用 A.__shared

0

这里有一个解决方案——这个方法可以为每个类设置独立的锁,因为它是在类的构造函数层面上实现的(元类)。感谢大家的提示和帮助,让我能写出这样的代码,效果看起来非常不错。

我也可以把变量搞混淆,但需要使用硬编码的'_A__lock',这可能会有问题,而且我还没有测试过。

import threading

class MetaA(type):
  def __new__(self, name, bases, clsDict):
    # change <type> behavior
    clsDict['_lock'] = threading.Lock()
    return super(MetaA, self).__new__(self, name, bases, clsDict)

class A(object):
  __metaclass__ = MetaA

  @classmethod
  def getLock(cls):
    return cls._lock

class B(A):
  pass

print 'id(A.getLock())', id(A.getLock())
print 'id(B.getLock())', id(B.getLock())
print A.getLock() == B.getLock()

撰写回答