18

この質問から、スレッドセーフなものが必要な場合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

(/擬似コード)

これは面倒なだけでなく、エラーが発生しやすくなります。それで、これにもっと良い方法でアプローチする方法についてのアイデア/提案はありますか?

4

4 に答える 4

58

これを実現するには、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()オブジェクト(依存性注入)を渡させます。

于 2012-11-29T03:26:48.883 に答える
2

[確かに、コメントを参照してください、それは真実ではありません]

CPythonを実行している場合は、設定されたソースコードから、GIL(http://hg.python.org/cpython/file/db20367b20de/Objects/setobject.c)がリリースされていないことがわかります。したがって、そのすべての操作は次のようになります。アトミック。

必要なものがすべて揃っていて、CPythonでコードを実行することが確実な場合は、直接使用できます。

于 2012-11-28T17:45:57.863 に答える
2

これはデコレータを試す最初の試みであり(私のコードは実際には@decorate構文を使用していませんが)、マルチスレッド/マルチプロセッシングの経験はあまりありません。ただし、その免責事項を使用して、私が行った試みは次のとおりです。

from multiprocessing import Lock

def decorate_all(obj):
    lock = Lock()
    #you'll want to make this more robust:
    fnc_names = [fnctn for fnctn in dir(obj) if '__' not in fnctn]
    for name in fnc_names:
        print 'decorating ' + name
        fnc = getattr(obj, name)
        setattr(obj, name, decorate(fnc, lock))
    return obj

def decorate(fnctn, lock):
    def decorated(*args):
        print 'acquiring lock'
        lock.acquire()
        try:
            print 'calling decorated function'
            return fnctn(*args)
        finally:
            print 'releasing lock'
            lock.release()
    return decorated


def thread_safe(superclass):
    lock = Lock()
    class Thread_Safe(superclass):
        def __init__(self, *args, **kwargs):
            super(Thread_Safe, self).__init__(*args, **kwargs)
    return decorate_all(Thread_Safe)


>>> thread_safe_set = thread_safe(set)
decorating add
decorating clear
decorating copy
decorating difference
decorating difference_update
decorating discard
decorating intersection
decorating intersection_update
decorating isdisjoint
decorating issubset
decorating issuperset
decorating pop
decorating remove
decorating symmetric_difference
decorating symmetric_difference_update
decorating union
decorating update
>>> s = thread_safe_set()
>>> s.add(1)
acquiring lock
calling decorated function
releasing lock
>>> s.add(4)
acquiring lock
calling decorated function
releasing lock
>>> s.pop()
acquiring lock
calling decorated function
releasing lock
1
>>> s.pop()
acquiring lock
calling decorated function
releasing lock
4
>>>
于 2012-11-28T22:05:51.670 に答える
0

独自のコンテキストマネージャーを実装できます。

class LockableSet:
    def __enter__(self):
        self.lock()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        #Do what you want with the error
        self.unlock()

with LockableSet() as s:
    s.whatever()
    raise Exception()

いずれにせよ、オブジェクトの__exit__メソッドは最後に呼び出されます。より詳細な情報はここにあります(python公式ドキュメント)。

これの別の用途は、次lockのようなメソッドのデコレータである可能性があります。

def lock(func):
    def safe_func(self, *args, **kwargs):
        with self:
            func(self, *args, **kwargs)
    return safe_func
于 2012-11-28T17:33:45.203 に答える