在类方法Python

时间:2017-05-25 16:47:17

标签: python methods multiprocessing

最初,我有一个类来存储一些已处理的值,并使用其他方法重用它们。

问题是当我试图将类方法划分为多个进程以加速,python生成的进程但似乎不起作用(正如我在任务管理器中看到的那样只有一个进程正在运行)并且结果永远不会被传递。

我做了几次搜索,发现pathos.multiprocessing可以做到这一点,但我想知道标准库是否可以解决这个问题?

from multiprocessing import Pool

class A():
    def __init__(self, vl):
        self.vl = vl
    def cal(self, nb):
        return nb * self.vl
    def run(self, dt):
        t = Pool(processes=4)
        rs = t.map(self.cal, dt)
        t.close()
        return t

a = A(2)

a.run(list(range(10)))

2 个答案:

答案 0 :(得分:7)

您的代码失败,因为它不能pickle实例方法(self.cal),这是Python在您通过将多个进程映射到multiprocessing.Pool(井)生成多个进程时尝试执行的操作,有一种方法可以做到这一点,但它太复杂了,无论如何都不是非常有用) - 由于没有共享内存访问,它必须“打包”数据并将其发送到生成的进程以进行解包。如果您试图挑选a实例,也会发生同样的情况。

multiprocessing包中唯一可用的共享内存访问权限鲜为人知multiprocessing.pool.ThreadPool,所以如果你真的想这样做:

from multiprocessing.pool import ThreadPool

class A():
    def __init__(self, vl):
        self.vl = vl
    def cal(self, nb):
        return nb * self.vl
    def run(self, dt):
        t = ThreadPool(processes=4)
        rs = t.map(self.cal, dt)
        t.close()
        return rs

a = A(2)
print(a.run(list(range(10))))
# prints: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

但这不会给你并行化,因为它本质上映射到可以访问共享内存的常规线程。您应该传递类/静态方法(如果需要它们),并附带您希望它们使用的数据(在您的情况下为self.vl)。如果您需要跨进程共享该数据,则必须使用某些共享内存抽象,例如multiprocessing.Value,当然要应用互斥。

<强>更新

我说过你可以做到(并且有些模块或多或少都在做,例如检查pathos.multiprocessing)但是我不认为值得这么麻烦 - 当你到达某个地方时必须欺骗你的系统做你想做的事情,你可能要么使用错误的系统,要么重新考虑你的设计。但是为了明智,这里有一种方法可以在多处理设置中执行您想要的操作:

import sys
from multiprocessing import Pool

def parallel_call(params):  # a helper for calling 'remote' instances
    cls = getattr(sys.modules[__name__], params[0])  # get our class type
    instance = cls.__new__(cls)  # create a new instance without invoking __init__
    instance.__dict__ = params[1]  # apply the passed state to the new instance
    method = getattr(instance, params[2])  # get the requested method
    args = params[3] if isinstance(params[3], (list, tuple)) else [params[3]]
    return method(*args)  # expand arguments, call our method and return the result

class A(object):

    def __init__(self, vl):
        self.vl = vl

    def cal(self, nb):
        return nb * self.vl

    def run(self, dt):
        t = Pool(processes=4)
        rs = t.map(parallel_call, self.prepare_call("cal", dt))
        t.close()
        return rs

    def prepare_call(self, name, args):  # creates a 'remote call' package for each argument
        for arg in args:
            yield [self.__class__.__name__, self.__dict__, name, arg]

if __name__ == "__main__":  # important protection for cross-platform use
    a = A(2)
    print(a.run(list(range(10))))
    # prints: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

我认为这是非常自我解释它是如何工作的,但简而言之,它传递了你的类的名称,它的当前状态(sans信号,tho),一个要调用的方法和一个调用它的参数{{ 1}}为parallel_call中的每个进程调用的函数。 Python自动pickle和unpickle所有这些数据,因此所有Pool需要做的是重建原始对象,在其中找到所需的方法并使用提供的param调用它。

这样我们只传递数据而不尝试传递活动对象,因此Python不会抱怨(在这种情况下,尝试在类参数中添加对实例方法的引用,看看会发生什么)和一切工作得很好。

如果你想对'魔术'嗤之以鼻,你可以使它看起来与你的代码完全一样(创建你自己的parallel_call处理程序,从函数中选取名称并将名称发送给实际进程等。 )但这应该为你的例子提供足够的功能。

但是,在提高您的希望之前,请记住,这只有在共享“静态”实例(一旦在多处理上下文中开始调用它时不会更改其初始状态的实例)时才会起作用。如果Pool方法是要更改A.cal属性的内部状态 - 它只会影响它发生更改的实例(除非它在调用之间调用vl的主实例中发生更改)。如果您也希望共享状态,可以在调用后升级Pool以获取parallel_call,并将其与方法调用结果一起返回,然后在调用方面,您必须更新带有返回数据的本地instance.__dict__来更改原始状态。这还不够 - 您实际上必须创建一个共享字典并处理所有互斥体工作人员以便所有进程同时访问它(您可以使用__dict__)。

所以,正如我所说,比它的价值更麻烦...

答案 1 :(得分:0)

  

问题:它似乎没有用(正如我在任务管理器中看到的那样,只有一个进程正在运行)   结果永远不会传递。

仅查看1个流程,因为Pool计算已使用流程的数量,如下所示:
您提供range(10) =任务索引0..9,因此Pool计算(10 / 4) * 4 = 8+1 = 9 启动第一个 process后,不再有任务 使用range(32),您会看到 4 process正在运行。

您将返回return t,而不是返回rs = pool.map(...的结果。

这将起作用,例如

def cal(self, nb):
    import os
    print('pid:{} cal({})'.format(os.getpid(), nb))
    return nb * self.vl

def run(self,df):
    with mp.Pool(processes=4) as pool:
       return pool.map(self.cal, df)

if __name__ == '__main__':
    a = A(2)
    result = a.run(list(range(32)))
    print(result)

使用Python测试:3.4.2