我从这个问题中了解到,如果我想拥有一个set线程安全的线程,则必须自己实现线程安全部分。
set
因此,我可以提出:
from threading import Lock class LockedSet(set): """A set where add() and remove() are thread-safe""" def __init__(self, *args, **kwargs): # Create a lock self._lock = Lock() # Call the original __init__ super(LockedSet, self).__init__(*args, **kwargs) def add(self, elem): self._lock.acquire() try: super(LockedSet, self).add(elem) finally: self._lock.release() def remove(self, elem): self._lock.acquire() try: super(LockedSet, self).remove(elem) finally: self._lock.release()
因此,在此实现中,当然只有add()和remove()是线程安全的。其他方法不是因为它们未在子类中覆盖。
现在,模式非常简单:获取锁,调用原始方法,释放锁。如果遵循上述逻辑,则必须以set基本上相同的方式覆盖所有公开的方法,例如:
(伪代码)
def <method>(<args>): 1. acquire lock 2. try: 3. call original method passing <args> 4. finally: 5. release lock
(/伪代码)
这不仅繁琐,而且容易出错。那么,关于如何更好地解决这一问题的任何想法/建议吗?
您可以使用Python的元编程工具来完成此任务。(注意:写得很快,没有经过全面测试。)我更喜欢使用类装饰器。
我还认为您 可能 需要锁定多个对象add并remove设置一个线程安全的集合,但是我不确定。我将忽略该问题,仅关注您的问题。
add
remove
还应考虑委派(代理)是否比子类更好。包装对象是Python中的常用方法。
最后,没有元编程的“魔杖”会神奇地向任何可变的Python集合添加细粒度的锁定。最安全的方法是使用锁定 任何 方法或属性访问RLock,但这是非常粗糙且缓慢的,并且可能仍不能保证您的对象在所有情况下都是线程安全的。(例如,您可能有一个集合,该集合操作另一个可访问其他线程的非线程安全对象。)您确实确实需要检查每个数据结构,并考虑哪些操作是原子操作或需要锁,以及哪些方法可能调用其他方法。使用相同的锁(即死锁本身)。
RLock
就是说,这里有一些以抽象顺序递增的方式供您使用:
class LockProxy(object): def __init__(self, obj): self.__obj = obj self.__lock = RLock() # RLock because object methods may call own methods def __getattr__(self, name): def wrapped(*a, **k): with self.__lock: getattr(self.__obj, name)(*a, **k) return wrapped lockedset = LockProxy(set([1,2,3]))
class LockedSet(set): """A set where add(), remove(), and 'in' operator are thread-safe""" def __init__(self, *args, **kwargs): self._lock = Lock() super(LockedSet, self).__init__(*args, **kwargs) def add(self, elem): with self._lock: super(LockedSet, self).add(elem) def remove(self, elem): with self._lock: super(LockedSet, self).remove(elem) def __contains__(self, elem): with self._lock: super(LockedSet, self).__contains__(elem)
def locked_method(method): """Method decorator. Requires a lock object at self._lock""" def newmethod(self, *args, **kwargs): with self._lock: return method(self, *args, **kwargs) return newmethod class DecoratorLockedSet(set): def __init__(self, *args, **kwargs): self._lock = Lock() super(DecoratorLockedSet, self).__init__(*args, **kwargs) @locked_method def add(self, *args, **kwargs): return super(DecoratorLockedSet, self).add(elem) @locked_method def remove(self, *args, **kwargs): return super(DecoratorLockedSet, self).remove(elem)
我认为这是抽象方法最清晰,最容易理解的方法,因此我对其进行了扩展,以允许它指定要锁定的方法和一个锁定对象工厂。
def lock_class(methodnames, lockfactory): return lambda cls: make_threadsafe(cls, methodnames, lockfactory) def lock_method(method): if getattr(method, '__is_locked', False): raise TypeError("Method %r is already locked!" % method) def locked_method(self, *arg, **kwarg): with self._lock: return method(self, *arg, **kwarg) locked_method.__name__ = '%s(%s)' % ('lock_method', method.__name__) locked_method.__is_locked = True return locked_method def make_threadsafe(cls, methodnames, lockfactory): init = cls.__init__ def newinit(self, *arg, **kwarg): init(self, *arg, **kwarg) self._lock = lockfactory() cls.__init__ = newinit for methodname in methodnames: oldmethod = getattr(cls, methodname) newmethod = lock_method(oldmethod) setattr(cls, methodname, newmethod) return cls @lock_class(['add','remove'], Lock) class ClassDecoratorLockedSet(set): @lock_method # if you double-lock a method, a TypeError is raised def frobnify(self): pass
__getattribute__
class AttrLockedSet(set): def __init__(self, *args, **kwargs): self._lock = Lock() super(AttrLockedSet, self).__init__(*args, **kwargs) def __getattribute__(self, name): if name in ['add','remove']: # note: makes a new callable object "lockedmethod" on every call # best to add a layer of memoization lock = self._lock def lockedmethod(*args, **kwargs): with lock: return super(AttrLockedSet, self).__getattribute__(name)(*args, **kwargs) return lockedmethod else: return super(AttrLockedSet, self).__getattribute__(name)
__new__
class NewLockedSet(set): def __new__(cls, *args, **kwargs): # modify the class by adding new unbound methods # you could also attach a single __getattribute__ like above for membername in ['add', 'remove']: def scoper(membername=membername): # You can also return the function or use a class def lockedmethod(self, *args, **kwargs): with self._lock: m = getattr(super(NewLockedSet, self), membername) return m(*args, **kwargs) lockedmethod.__name__ = membername setattr(cls, membername, lockedmethod) self = super(NewLockedSet, cls).__new__(cls, *args, **kwargs) self._lock = Lock() return self
__metaclass__
def _lockname(classname): return '_%s__%s' % (classname, 'lock') class LockedClass(type): def __new__(mcls, name, bases, dict_): # we'll bind these after we add the methods cls = None def lockmethodfactory(methodname, lockattr): def lockedmethod(self, *args, **kwargs): with getattr(self, lockattr): m = getattr(super(cls, self), methodname) return m(*args,**kwargs) lockedmethod.__name__ = methodname return lockedmethod lockattr = _lockname(name) for methodname in ['add','remove']: dict_[methodname] = lockmethodfactory(methodname, lockattr) cls = type.__new__(mcls, name, bases, dict_) return cls def __call__(self, *args, **kwargs): #self is a class--i.e. an "instance" of the LockedClass type instance = super(LockedClass, self).__call__(*args, **kwargs) setattr(instance, _lockname(self.__name__), Lock()) return instance class MetaLockedSet(set): __metaclass__ = LockedClass
def LockedClassMetaFactory(wrapmethods): class LockedClass(type): def __new__(mcls, name, bases, dict_): # we'll bind these after we add the methods cls = None def lockmethodfactory(methodname, lockattr): def lockedmethod(self, *args, **kwargs): with getattr(self, lockattr): m = getattr(super(cls, self), methodname) return m(*args,**kwargs) lockedmethod.__name__ = methodname return lockedmethod lockattr = _lockname(name) for methodname in wrapmethods: dict_[methodname] = lockmethodfactory(methodname, lockattr) cls = type.__new__(mcls, name, bases, dict_) return cls def __call__(self, *args, **kwargs): #self is a class--i.e. an "instance" of the LockedClass type instance = super(LockedClass, self).__call__(*args, **kwargs) setattr(instance, _lockname(self.__name__), Lock()) return instance return LockedClass class MetaFactoryLockedSet(set): __metaclass__ = LockedClassMetaFactory(['add','remove'])
我敢打赌,现在使用一个简单明了的方法try...finally看起来还不错,对吧?
try...finally
读者的练习:Lock()使用这些方法中的任何一种,让调用者传递他们自己的对象(依赖注入)。
Lock()