首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何用互斥锁修饰Python对象

如何用互斥锁修饰Python对象
EN

Stack Overflow用户
提问于 2013-04-12 07:56:34
回答 4查看 3.7K关注 0票数 6

我是python的新手,目前正在尝试学习线程。我厌倦了使用锁来使我的资源线程安全,因为它们本身并不绑定到资源,所以每次我的代码与资源交互时,我一定会忘记获取和/或释放它们。相反,我希望能够“包装”(或装饰?)一个对象,这样它的所有方法和属性getter/setter都是原子的。如下所示:

代码语言:javascript
复制
state = atomicObject(dict())

# the following is atomic/thread-safe
state["some key"] = "some value"

这个是可能的吗?如果是这样,实现它的“最佳实践”方式是什么?

编辑:上述问题的一个很好的答案可以在How to make built-in containers (sets, dicts, lists) thread safe?中找到。然而,正如abarnert和jsbueno所展示的那样,我提出的解决方案(自动化锁)通常不是一个好主意,因为确定原子操作的适当粒度需要一些智能,并且很可能很难(或不可能)正确地实现自动化。

问题仍然存在,锁没有以任何方式绑定到它们应该保护的资源,所以我的新问题是:什么是将锁与对象关联的好方法?

建议的解决方案#2:我想可能有一种方法可以将锁绑定到对象,这样在没有首先获取锁的情况下尝试访问该对象会抛出错误,但我可以看到这是如何变得棘手的。

编辑:以下代码与问题不太相关。我发布它是为了证明我曾试图自己解决这个问题,但在发布这个问题之前迷失了方向。

根据记录,我编写了以下代码,但它不起作用:

代码语言:javascript
复制
import threading    
import types
import inspect

class atomicObject(object):

    def __init__(self, obj):
        self.lock = threading.RLock()
        self.obj = obj

        # keep track of function handles for lambda functions that will be created
        self.funcs = []

        # loop through all the attributes of the passed in object
        # and create wrapped versions of each attribute
        for name in dir(self.obj):
            value = getattr(self.obj, name)
            if inspect.ismethod(value):
                # this is where things get really ugly as i try to work around the
                # limitations of lambda functions and use eval()... I'm not proud of this code
                eval("self.funcs.append(lambda self, *args, **kwargs: self.obj." + name + "(*args, **kwargs))")
                fidx = str(len(self.funcs) - 1)
                eval("self." + name + " = types.MethodType(lambda self, *args, **kwargs: self.atomize(" + fidx + ", *args, **kwargs), self)")

    def atomize(self, fidx, *args, **kwargs):
        with self.lock:
            return self.functions[fidx](*args, **kwargs)

我可以创建一个atomicObject(dict()),但是当我尝试向对象添加值时,我得到错误消息:"atomicObject不支持项分配“。

EN

回答 4

Stack Overflow用户

发布于 2013-04-12 09:45:03

我在你的问题上花了一些心思,这将是一个棘手的问题-你不仅要用你的原子类代理所有的对象方法,这可以通过正确地编写__getattribute__方法来完成-但是为了让操作符本身工作,你还必须给被代理的对象提供一个类,这个类提供了与原始的对象类相同的“魔术双下划线”方法-也就是说,你必须动态地创建一个代理类-否则操作符的使用本身就不是原子的。

这是可行的-但由于您是Python的新手,您可以在交互提示符上执行import this,并且在出现的几个指南/建议中,您将看到:“如果实现很难解释,这不是一个好主意。”:-)

这就引出了一个问题:在Python中使用线程通常不是一个好主意。除了带有大量阻塞I/O的准平凡代码--你会更喜欢另一种方法--Python线程化不允许普通-as代码使用更多的CPU核,例如--一次只有一个Python代码线程在运行--搜索"Python GIL“来了解原因-(例外,如果你的大量代码都花在计算密集型的本机代码上,比如Numpy函数)。

但是您更愿意使用各种可用的框架来编写程序来使用异步调用,或者为了更容易地利用多个内核,使用multiprocessing而不是threading -它基本上为每个“线程”创建一个进程-并且要求所有的共享都是显式完成的。

票数 2
EN

Stack Overflow用户

发布于 2019-05-31 10:15:26

然后,wrapt模块包含此处描述的@synchronized装饰器。

https://pypi.python.org/pypi/wrapt

描述装饰器及其工作原理的演讲可以在以下位置找到:

票数 1
EN

Stack Overflow用户

发布于 2013-04-12 10:25:07

尽管我的另一个答案-它有关于Python线程的合理考虑,并将现有对象转换为“原子”锁定对象-但如果您定义要原子锁定的对象的类,整个事情就简单了一个数量级。

你可以做一个函数装饰器,让函数用四行锁来运行。这样就可以构建一个类装饰器,它自动锁定给定类的所有方法和属性。

下面的代码适用于Python 2和3(我在函数调用中使用了@abarnet的示例,在类示例中依赖于我的“打印调试”。)

代码语言:javascript
复制
import threading
from functools import wraps

#see http://stackoverflow.com/questions/15960881/how-to-decorate-a-python-object-with-a-mutex/15961762#15960881

printing = False

lock = threading.Lock()
def atomize(func):
    @wraps(func)
    def wrapper(*args, **kw):
        with lock:
            if printing:
                print ("atomic")
            return func(*args, **kw)
    return wrapper

def Atomic(cls):
    new_dict = {}
    for key, value in cls.__dict__.items():
        if hasattr(value, "__get__"):
            def get_atomic_descriptor(desc):
                class Descriptor(object):
                    @atomize
                    def __get__(self, instance, owner):
                        return desc.__get__(instance, owner)
                    if hasattr(desc, "__set__"):
                        @atomize
                        def __set__(self, instance, value):
                            return desc.__set__(instance, value)
                    if hasattr(desc, "__delete__"):
                        @atomize
                        def __delete__(self, instance):
                            return desc.__delete__(instance)
                return Descriptor()
            new_dict[key] = get_atomic_descriptor(value)
        elif callable(value):
            new_dict[key] = atomize(value)
        else:
            new_dict[key] = value
    return type.__new__(cls.__class__, cls.__name__, cls.__bases__, new_dict)


if __name__ == "__main__": # demo:
    printing = True

    @atomize
    def sum(a,b):
        return a + b

    print (sum(2,3))

    @Atomic
    class MyObject(object):
        def _get_a(self):
            return self.__a

        def _set_a(self, value):
            self.__a = value +  1

        a = property(_get_a, _set_a)

        def smurf(self, b):
            return self.a + b

    x = MyObject()
    x.a = 5
    print(x.a)
    print (x.smurf(10))

    # example of atomized function call - based on
    # @abarnet's code at http://pastebin.com/MrtR6Ufh
    import time, random
    printing = False
    x = 0

    def incr():
        global x
        for i in range(100):
            xx = x
            xx += 1
            time.sleep(random.uniform(0, 0.02))
            x = xx

    def do_it():
        threads = [threading.Thread(target=incr) for _ in range(20)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

    do_it()
    print("Unlocked Run: ", x)

    x = 0
    incr = atomize(incr)
    do_it()
    print("Locked Run: ", x)

注意:虽然"eval“和"exec”在Python中可用,但严肃的代码很少--我是说很少--也不需要。即使是重新创建函数的复杂装饰器也可以通过内省进行dos,而不是依赖于通过eval进行字符串编译。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15960881

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档