如何使内置容器(集合,字典,列表)具有线程安全性?

发布于 2021-01-29 19:35:55

我从这个问题中了解到,如果我想拥有一个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

(/伪代码)

这不仅繁琐,而且容易出错。那么,关于如何更好地解决这一问题的任何想法/建议吗?

关注者
0
被浏览
68
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    您可以使用Python的元编程工具来完成此任务。(注意:写得很快,没有经过全面测试。)我更喜欢使用类装饰器。

    我还认为您 可能 需要锁定多个对象addremove设置一个线程安全的集合,但是我不确定。我将忽略该问题,仅关注您的问题。

    还应考虑委派(代理)是否比子类更好。包装对象是Python中的常用方法。

    最后,没有元编程的“魔杖”会神奇地向任何可变的Python集合添加细粒度的锁定。最安全的方法是使用锁定 任何
    方法或属性访问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看起来还不错,对吧?

    读者的练习:Lock()使用这些方法中的任何一种,让调用者传递他们自己的对象(依赖注入)。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看