多处理在进程之间共享不可序列化的对象

时间:2014-02-23 12:37:36

标签: python python-3.x proxy multiprocessing concurrent.futures

有三个问题可能重复(但过于具体):

通过回答这个问题,可以回答所有其他三个问题。 希望我能说清楚:

一旦我在多处理创建的某个过程中创建了一个对象:

  1. 如何将引用传递给其他进程?
  2. (不是那么重要)我如何确保在持有参考资料时此过程不会消失?
  3. 示例1(已解决)

    from concurrent.futures import *
    
    def f(v):
        return lambda: v * v
    
    if __name__ == '__main__':
        with ThreadPoolExecutor(1) as e: # works with ThreadPoolExecutor
            l = list(e.map(f, [1,2,3,4]))
        print([g() for g in l]) # [1, 4, 9, 16]
    

    示例2

    假设f返回具有可变状态的对象。应该可以从其他进程访问此相同的对象。

    示例3

    我有一个具有打开文件和锁的对象 - 如何授予对其他进程的访问权限?

    提醒

    我不希望出现此特定错误。或者是这个特定用例的解决方案。解决方案应足够通用,以便在进程之间共享不可移动的对象。可以在任何进程中创建对象。使所有对象可移动并保持身份的解决方案也很好。

    任何提示都是受欢迎的,任何指向如何实现解决方案的部分解决方案或代码片段都是值得的。所以我们可以一起创建解决方案。

    这是一个尝试来解决此问题,但没有多处理:https://github.com/niccokunzmann/pynet/blob/master/documentation/done/tools.rst

    问题

      

    您希望其他流程对引用做什么?

    可以将引用传递给使用多处理创建的任何其他进程(副本3)。可以访问属性,调用引用。访问的属性可能是也可能不是代理。

      

    使用代理有什么问题?

    也许没有问题,只有挑战。我的印象是代理有一个管理器,管理器有自己的进程,因此必须序列化和转移不可序列化的对象(部分用StacklessPython / fork解决)。 还存在特殊对象的代理 - 为所有对象(可解决的)构建代理很难但不是不可能。

    解决方案? - 代理+经理?

    Eric Urban表明序列化不是问题所在。真正的挑战在于Example2& 3:状态的同步。我对解决方案的想法是为经理创建一个特殊的代理类。这个代理类

    1. 为不可序列化的对象采用构造函数
    2. 获取可序列化对象并将其传输到管理器进程。
    3. (问题)根据1.必须在经理过程中创建不可序列化的对象。

3 个答案:

答案 0 :(得分:10)

大多数情况下,不希望将现有对象的引用传递给另一个进程。而是创建要在进程之间共享的类:

class MySharedClass:
    # stuff...

然后你创建一个像这样的代理管理器:

import multiprocessing.managers as m
class MyManager(m.BaseManager):
    pass # Pass is really enough. Nothing needs to be done here.

然后您在该经理上注册您的课程,如下所示:

MyManager.register("MySharedClass", MySharedClass)

然后,一旦管理员实例化并启动,使用manager.start(),您就可以使用manager.MySharedClass创建班级的共享实例。这应该适用于所有需求。返回的代理与原始对象的工作方式完全相同,但documentation中描述的一些例外情况除外。

答案 1 :(得分:4)

在阅读本答案之前,请注意其中解释的解决方案很糟糕。请在答案结尾处注明警告。

我找到了一种通过multiprocessing.Array分享对象状态的方法。 所以我创建了这个透明地通过所有进程共享状态的类:

import multiprocessing as m
import pickle

class Store:
    pass

class Shareable:
    def __init__(self, size = 2**10):
        object.__setattr__(self, 'store', m.Array('B', size))
        o = Store() # This object will hold all shared values
        s = pickle.dumps(o)
        store(object.__getattribute__(self, 'store'), s)

    def __getattr__(self, name):
        s = load(object.__getattribute__(self, 'store'))
        o = pickle.loads(s)
        return getattr(o, name)

    def __setattr__(self, name, value):
        s = load(object.__getattribute__(self, 'store'))
        o = pickle.loads(s)
        setattr(o, name, value)
        s = pickle.dumps(o)
        store(object.__getattribute__(self, 'store'), s)

def store(arr, s):
    for i, ch in enumerate(s):
        arr[i] = ch

def load(arr):
    l = arr[:]
    return bytes(arr)

您可以将此类(及其子类)的实例传递给任何其他进程,并通过所有进程同步它的状态。 使用此代码对此进行了测试:

class Foo(Shareable):
    def __init__(self):
        super().__init__()
        self.f = 1

    def foo(self):
        self.f += 1

def f(s):
    s.f += 1

if __name__ == '__main__':
    import multiprocessing as m
    import time
    s = Foo()
    print(s.f)
    p = m.Process(target=f, args=(s,))
    p.start()
    time.sleep(1)
    print(s.f)

这个类的“神奇之处”在于它将所有属性存储在类Store的另一个实例中。这堂课不是很特别。它只是一些可以拥有任意属性的类。 (dict也可以。)

然而,这堂课有一些非常讨厌的怪癖。我找到了两个。

第一个怪癖是你必须指定Store个实例最多需要多少空间。这是因为multiprocessing.Array具有静态大小。因此,可以在其中进行腌制的对象只能与数组一样大。

第二个怪癖是你不能将这个类与ProcessPoolExecutors或简单的Pools一起使用。如果您尝试这样做,则会收到错误:

>>> s = Foo()
>>> with ProcessPoolExecutor(1) as e:
...     e.submit(f, args=(s,))
... 
<Future at 0xb70fe20c state=running>
Traceback (most recent call last):
<omitted>
RuntimeError: SynchronizedArray objects should only be shared between processes through inheritance

警告
您可能不应该使用这种方法,因为它使用无法控制的内存量,与使用代理相比过于复杂(请参阅我的其他答案)并且可能会以惊人的方式崩溃。

答案 2 :(得分:3)

只使用无堆栈python。您可以使用pickle序列化几乎所有内容,包括函数。在这里,我使用lambda模块对pickle进行序列化和反序列化。这类似于您在示例中尝试执行的操作。

这是Stackless Python http://www.stackless.com/wiki/Download

的下载链接
Python 2.7.5 Stackless 3.1b3 060516 (default, Sep 23 2013, 20:17:03) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> f = 5
>>> g = lambda : f * f
>>> g()
25
>>> import pickle
>>> p = pickle.dumps(g)
>>> m = pickle.loads(p)
>>> m()
25
>>>