当前位置: 首页 > 面试题库 >

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

曹自怡
2023-03-14
问题内容

我从这个问题中了解到,如果我想拥有一个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的元编程工具来完成此任务。(注意:写得很快,没有经过全面测试。)我更喜欢使用类装饰器。

我还认为您 可能 需要锁定多个对象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()使用这些方法中的任何一种,让调用者传递他们自己的对象(依赖注入)。



 类似资料:
  • 我从这个问题中了解到,如果我想要一个线程安全的,我必须自己实现线程安全部分。 因此,我可以想出: 因此,在这个实现中,当然只有add()和remove()是线程安全的。其他方法不是,因为它们在子类中没有被覆盖。 现在,模式非常简单:获取锁,调用原始方法,释放锁。如果遵循上述逻辑,我必须以基本相同的方式覆盖公开的所有方法,例如: (伪代码) (/伪代码) 这不仅单调乏味,而且容易出错。那么,关于如何

  • 问题内容: 我想知道Python内置容器(列表,向量,集合…)是否是线程安全的?还是我需要为共享变量实现锁定/解锁环境? 问题答案: 您需要为将在Python中修改的所有共享变量实现自己的锁定。你不必担心从不会被修改的变量读(即并发读取都OK了),所以稳定的类型(,,)都 可能是 安全的,但它不会伤害。对于你将要改变的东西- ,,,和大多数其他的对象,你应该有自己的锁定机制(而就地操作都OK在大多

  • 问题内容: 带有字典列表,例如: 将它们组合到以下词典列表中的最佳方法是什么: 还是最好以其他方式存储数据,例如: 谢谢你的帮助。 问题答案: 这是一个可能的解决方案: 产生: 如您所见,我使用字典(“合并”)作为中途点。当然,您可以通过以不同的方式存储数据来跳过步骤,但这还取决于这些变量可能具有的其他用途。 祝一切顺利。

  • 问题内容: 我有一堂课,有一本字典 我正在运行4个线程(每个餐厅一个线程)来调用方法。这是每个线程运行的函数: 这样安全吗,还是在致电之前必须使用锁? 问题答案: Python的内置结构对于单个操作是线程安全的,但是有时很难看到一条语句真正变成了多个操作。 您的代码应该是安全的。注意:这里的锁几乎不会增加任何开销,并且让您高枕无忧。 http://effbot.org/pyfaq/what-kin

  • 问题内容: 有人说python字典是线程安全的。这是否意味着我可以或不能在字典中修改项目? 问题答案: 这两个概念完全不同。 线程安全性意味着两个线程无法同时修改同一对象,从而使系统处于不一致状态。 也就是说,在迭代字典时不能修改字典。请参阅文档。。 字典p在迭代过程中不应被突变。在字典上进行迭代时,修改键的值是安全的(自Python 2.1起),但前提是只要键的集合不变即可。

  • 问题内容: 我使用以下代码将字符串添加到列表框中。当我运行代码并打开窗口时,由于窗口不够大,因此会截断较长的字符串(请参见屏幕截图)。我试图使窗口可调整大小并添加滚动条,但是我想知道是否有一种自动调整窗口大小以适合内容的方法。 问题答案: 重置列表框宽度对我有用。我使用了遗忘的答案,并注意到宽度始终为零。 我还建议在重新加载列表内容后重置根窗口的几何形状。否则,如果用户手动扩展窗口,则该窗口将停止