多处理管理器和自定义类

时间:2017-10-16 22:15:24

标签: python multiprocessing

我不知道为什么,但每当我尝试传递给共享对象共享自定义类对象的方法时,我都会遇到这个奇怪的错误。 Python版本:3.6.3

代码:

from multiprocessing.managers import SyncManager

class MyManager(SyncManager): pass
class MyClass: pass

class Wrapper:
    def set(self, ent):
        self.ent = ent

MyManager.register('MyClass', MyClass)
MyManager.register('Wrapper', Wrapper)

if __name__ == '__main__':
    manager = MyManager()
    manager.start()

    try:
        obj = manager.MyClass()
        lst = manager.list([1,2,3])

        collection = manager.Wrapper()
        collection.set(lst) # executed fine
        collection.set(obj) # raises error
    except Exception as e:
        raise

错误:

---------------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\Program Files\Python363\lib\multiprocessing\managers.py", line 228, in serve_client
    request = recv()
  File "D:\Program Files\Python363\lib\multiprocessing\connection.py", line 251, in recv
    return _ForkingPickler.loads(buf.getbuffer())
  File "D:\Program Files\Python363\lib\multiprocessing\managers.py", line 881, in RebuildProxy
    return func(token, serializer, incref=incref, **kwds)
TypeError: AutoProxy() got an unexpected keyword argument 'manager_owned'
---------------------------------------------------------------------------

这里的问题是什么?

3 个答案:

答案 0 :(得分:8)

我也遇到了这个问题,如前所述,这是Python multiprocessing(请参阅issue #30256)中的一个错误,而纠正该错误的pull request尚未合并。

除了手动修补本地安装之外,还有其他三个选项:

  • 您可以使用MakeProxyType()可调用来指定您的代理类型,而不必依赖AutoProxy代理生成器,
  • 您可以定义一个自定义代理类,
  • 您可以使用Monkeypatch修补错误

在解释AutoProxy的作用之后,我将在下面描述这些选项:

AutoProxy类的意义是什么

多处理Manager模式通过将所有值置于相同的专用“规范值服务器”过程中来访问共享值。所有其他进程(客户端)都通过代理与服务器通信,然后与服务器来回传递消息。

服务器确实需要知道对象类型可以接受哪些方法,因此,客户端可以使用相同的方法来生成代理对象。这就是AutoProxy对象的作用。每当客户端需要您的注册类的新实例时,客户端创建的默认代理就是AutoProxy,然后它将要求服务器告诉它可以使用哪些方法。

一旦有了方法名称,它将调用MakeProxyType来构造一个新类,然后为该类创建一个实例以返回。

所有这些都推迟到您实际上需要代理类型的实例为止,因此原则上AutoProxy如果不使用已注册的某些类,则可以节省一点内存。但是,它的内存很少,缺点是该过程必须在每个客户端过程中进行。

这些代理对象使用引用计数来跟踪服务器何时可以删除规范值。就是AutoProxy可调用部分中被破坏的部分;当在服务器进程(而不是客户端)中创建代理对象时,将新的参数传递给代理类型以禁用引用计数,但是AutoProxy类型并未更新以支持此功能。

那么,如何解决这个问题?这是这3个选项:

使用MakeProxyType()可调用对象

如前所述,AutoProxy实际上只是一个(通过服务器)获取该类型的公共方法的调用,以及对MakeProxyType()的调用。注册时,您可以自己拨打这些电话。

所以,而不是

from multiprocessing.managers import SyncManager
SyncManager.register("YourType", YourType)

使用

from multiprocessing.managers import SyncManager, MakeProxyType, public_methods
#               arguments:    classname,  sequence of method names
YourTypeProxy = MakeProxyType("YourType", public_methods(YourType))
SyncManager.register("YourType", YourType, YourTypeProxy)

可以随时在其中插入MakeProxyType()呼叫。

如果您使用exposed的{​​{1}}参数,则应将这些名称传递给SyncManager.register()

MakeProxyType

对于所有预注册的类型,您也必须这样做:

# SyncManager.register("YourType", YourType, exposed=("foo", "bar"))
# becomes
YourTypeProxy = MakeProxyType("YourType", ("foo", "bar"))
SyncManager.register("YourType", YourType, YourTypeProxy)

创建自定义代理

您可以依靠多处理为您创建代理。您可以自己编写。对于特殊的“托管值”服务器进程,该代理用于所有进程 中,并且代理应来回传递消息。当然,这不是已经注册的类型的选项,但是我在这里提到它是因为对于您自己的类型,这提供了优化的机会。

请注意,对于所有需要返回到“规范”值实例的交互,您应该具有方法,因此您需要使用属性来处理常规属性或添加{{1} },from multiprocessing.managers import SyncManager, AutoProxy, MakeProxyType, public_methods registry = SyncManager._registry for typeid, (callable, exposed, method_to_typeid, proxytype) in registry.items(): if proxytype is not AutoProxy: continue create_method = hasattr(managers.SyncManager, typeid) if exposed is None: exposed = public_methods(callable) SyncManager.register( typeid, callable=callable, exposed=exposed, method_to_typeid=method_to_typeid, proxytype=MakeProxyType(f"{typeid}Proxy", exposed), create_method=create_method, ) __getattr__方法。

优点是您可以非常精确地控制与服务器进程实际交换数据的方法。在我的特定示例中,我的代理类缓存了不可变的信息(一旦创建对象,这些值就永远不会改变),但是经常使用。其中包括一个标志值,该标志值控制 other 方法是否会执行某些操作,因此代理可以只检查该标志值,如果未设置,则 not 与服务器进程对话。像这样:

__setattr__

由于__delattr__返回一个class FooProxy(BaseProxy): # what methods the proxy is allowed to access through calls _exposed_ = ("__getattribute__", "expensive_method", "spam") @property def flag(self): try: v = self._flag except AttributeError: # ask for the value from the server, "realvalue.flag" # use __getattribute__ because it's an attribute, not a property v = self._flag = self._callmethod("__getattribute__", ("flag",)) return flag def expensive_method(self, *args, **kwargs): if self.flag: # cached locally! return self._callmethod("expensive_method", args, kwargs) def spam(self, *args, **kwargs): return self._callmethod("spam", args, kwargs SyncManager.register("Foo", Foo, FooProxy) 子类,因此您可以将该类与自定义子类结合使用,从而省去编写任何仅由MakeProxyType()组成的方法的需要:

BaseProxy

同样,这不能解决嵌套在其他代理值内的标准类型的问题。

应用猴子补丁

我使用它来修复return self._callmethod(...)可调用项,当您正在运行已将修复程序应用于源代码的Python版本时,此应该自动避免打补丁:

# a base class with the methods generated for us. The second argument
# doubles as the 'permitted' names, stored as _exposed_
FooProxyBase = MakeProxyType(
    "FooProxyBase",
    ("__getattribute__", "expensive_method", "spam"),
)

class FooProxy(FooProxyBase):
    @property
    def flag(self):
        try:
            v = self._flag
        except AttributeError:
            # ask for the value from the server, "realvalue.flag"
            # use __getattribute__ because it's an attribute, not a property
            v = self._flag = self._callmethod("__getattribute__", ("flag",))
        return flag

    def expensive_method(self, *args, **kwargs):
        if self.flag:   # cached locally!
            return self._callmethod("expensive_method", args, kwargs)

    def spam(self, *args, **kwargs):
        return self._callmethod("spam", args, kwargs

SyncManager.register("Foo", Foo, FooProxy)

导入以上内容,然后调用AutoProxy函数来修复# Backport of https://github.com/python/cpython/pull/4819 # Improvements to the Manager / proxied shared values code # broke handling of proxied objects without a custom proxy type, # as the AutoProxy function was not updated. # # This code adds a wrapper to AutoProxy if it is missing the # new argument. import logging from inspect import signature from functools import wraps from multiprocessing import managers logger = logging.getLogger(__name__) orig_AutoProxy = managers.AutoProxy @wraps(managers.AutoProxy) def AutoProxy(*args, incref=True, manager_owned=False, **kwargs): # Create the autoproxy without the manager_owned flag, then # update the flag on the generated instance. If the manager_owned flag # is set, `incref` is disabled, so set it to False here for the same # result. autoproxy_incref = False if manager_owned else incref proxy = orig_AutoProxy(*args, incref=autoproxy_incref, **kwargs) proxy._owned_by_manager = manager_owned return proxy def apply(): if "manager_owned" in signature(managers.AutoProxy).parameters: return logger.debug("Patching multiprocessing.managers.AutoProxy to add manager_owned") managers.AutoProxy = AutoProxy # re-register any types already registered to SyncManager without a custom # proxy type, as otherwise these would all be using the old unpatched AutoProxy SyncManager = managers.SyncManager registry = managers.SyncManager._registry for typeid, (callable, exposed, method_to_typeid, proxytype) in registry.items(): if proxytype is not orig_AutoProxy: continue create_method = hasattr(managers.SyncManager, typeid) SyncManager.register( typeid, callable=callable, exposed=exposed, method_to_typeid=method_to_typeid, create_method=create_method, ) 。在启动管理器服务器之前 这样做!

答案 1 :(得分:3)

找到临时解决方案here。 我已经设法通过在 multiprocessing \ managers.py 中将所需的关键字添加到AutoProxy的初始化程序来修复它。但是,我不知道这个kwarg是否对任何事情负责。

答案 2 :(得分:1)

编辑多处理源代码的解决方案

Sergey的原始答案要求您编辑多处理源代码,如下所示:

  1. 找到您的多处理软件包(我的,通过Anaconda安装,位于/anaconda3/lib/python3.6/multiprocessing)。
  2. 打开managers.py
  3. 将关键参数manager_owned=True添加到AutoProxy函数。
  4. 原始AutoProxy:

    def AutoProxy(token, serializer, manager=None, authkey=None,
              exposed=None, incref=True):
        ...
    

    已编辑的AutoProxy:

    def AutoProxy(token, serializer, manager=None, authkey=None,
              exposed=None, incref=True, manager_owned=True):
        ...
    

    在运行时通过代码解决方案

    我设法解决了意外关键字参数 TypeError异常,而没有直接编辑multiprocessing的源代码,而是在我使用多处理管理器的地方添加这几行代码:

    import multiprocessing
    
    # Backup original AutoProxy function
    backup_autoproxy = multiprocessing.managers.AutoProxy
    
    # Defining a new AutoProxy that handles unwanted key argument 'manager_owned'
    def redefined_autoproxy(token, serializer, manager=None, authkey=None,
              exposed=None, incref=True, manager_owned=True):
        # Calling original AutoProxy without the unwanted key argument
        return backup_autoproxy(token, serializer, manager, authkey,
                         exposed, incref)
    
    # Updating AutoProxy definition in multiprocessing.managers package
    multiprocessing.managers.AutoProxy = redefined_autoproxy