在Python进程之间共享复杂对象?

2024-04-24 10:40:33 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个相当复杂的Python对象,需要在多个进程之间共享。我使用multiprocessing.Process启动这些进程。当我与对象中的multiprocessing.Queuemultiprocessing.Pipe共享对象时,它们就可以很好地共享了。但当我尝试与其他非多处理模块对象共享一个对象时,Python似乎会分叉这些对象。是真的吗?

我试过使用multiprocessing.Value。但我不确定应该是什么类型的?我的对象类称为MyClass。但当我尝试multiprocess.Value(MyClass, instance)时,它失败了:

TypeError: this type has no size

知道怎么回事吗?


Tags: 模块对象instance类型queue进程valuetype
3条回答

这里有一个python包,我正是为了这个(在进程之间共享复杂的对象)。

吉特:https://github.com/dRoje/pipe-proxy

其思想是为对象创建一个代理并将其传递给进程。然后使用代理,就像引用原始对象一样。尽管您只能使用方法调用,但是访问对象变量是通过抛出setter和getter完成的。

假设我们有一个名为“example”的对象,创建代理和代理侦听器很容易:

from pipeproxy import proxy 
example = Example() 
exampleProxy, exampleProxyListener = proxy.createProxy(example) 

现在将代理发送到另一个进程。

p = Process(target=someMethod, args=(exampleProxy,)) p.start()

在其他进程中使用它,就像使用原始对象一样(示例):

def someMethod(exampleProxy):
    ...
    exampleProxy.originalExampleMethod()
    ...

但你必须在主要过程中倾听:

exampleProxyListener.listen()

阅读更多内容并在此处查找示例:

http://matkodjipalo.com/index.php/2017/11/12/proxy-solution-python-multiprocessing/

经过大量的研究和测试,我发现“经理”是在一个非复杂的对象级别上完成这项工作的。

下面的代码显示对象inst在进程之间共享,这意味着当子进程更改它时,inst的属性var在外部更改。

from multiprocessing import Process, Manager
from multiprocessing.managers import BaseManager

class SimpleClass(object):
    def __init__(self):
        self.var = 0

    def set(self, value):
        self.var = value

    def get(self):
        return self.var


def change_obj_value(obj):
    obj.set(100)


if __name__ == '__main__':
    BaseManager.register('SimpleClass', SimpleClass)
    manager = BaseManager()
    manager.start()
    inst = manager.SimpleClass()

    p = Process(target=change_obj_value, args=[inst])
    p.start()
    p.join()

    print inst                    # <__main__.SimpleClass object at 0x10cf82350>
    print inst.get()              # 100

好的,如果只需要共享简单对象,上面的代码就足够了。

为什么不复杂?因为如果您的对象是嵌套的(对象内部),它可能会失败:

from multiprocessing import Process, Manager
from multiprocessing.managers import BaseManager

class GetSetter(object):
    def __init__(self):
        self.var = None

    def set(self, value):
        self.var = value

    def get(self):
        return self.var


class ChildClass(GetSetter):
    pass

class ParentClass(GetSetter):
    def __init__(self):
        self.child = ChildClass()
        GetSetter.__init__(self)

    def getChild(self):
        return self.child


def change_obj_value(obj):
    obj.set(100)
    obj.getChild().set(100)


if __name__ == '__main__':
    BaseManager.register('ParentClass', ParentClass)
    manager = BaseManager()
    manager.start()
    inst2 = manager.ParentClass()

    p2 = Process(target=change_obj_value, args=[inst2])
    p2.start()
    p2.join()

    print inst2                    # <__main__.ParentClass object at 0x10cf82350>
    print inst2.getChild()         # <__main__.ChildClass object at 0x10cf6dc50>
    print inst2.get()              # 100
    #good!

    print inst2.getChild().get()   # None
    #bad! you need to register child class too but there's almost no way to do it
    #even if you did register child class, you may get PicklingError :)

我认为这种行为的主要原因是因为Manager只是一个candybar构建在诸如pipe/queue之类的低级通信工具之上。

因此,对于多处理情况,这种方法是不推荐的。对于复杂的用例(只有我的推荐lol),最好使用低级工具,比如锁/信号量/管道/队列或高级工具,比如Redis queueRedis publish/subscribe

您可以使用Python的多处理“Manager”类和您定义的代理类来实现这一点。从Python文档: http://docs.python.org/library/multiprocessing.html#proxy-objects

您要做的是为您的自定义对象定义一个代理类,然后使用“远程管理器”共享该对象—请看“远程管理器”的同一链接文档页中的示例,文档显示了如何共享远程队列。您将要执行相同的操作,但是对您的_manager_instance.register()的调用将在其参数列表中包含您的自定义代理类。

以这种方式,您将设置一个服务器,以便与自定义代理共享自定义对象。您的客户机需要访问服务器(同样,请参阅有关如何设置对远程队列的客户机/服务器访问权限的优秀文档示例,但不是共享队列,而是共享对特定类的访问权限)。

相关问题 更多 >