python多处理:设置类属性值

时间:2013-11-07 05:29:05

标签: python multiprocessing

我有一个名为Experiment的类,另一个名为Case。一个实验由许多个案组成。请参阅下面的类定义,

from multiprocessing import Process

class Experiment (object):
    def __init__(self, name):
        self.name = name
        self.cases = []
        self.cases.append(Case('a'))
        self.cases.append(Case('b'))
        self.cases.append(Case('c'))

    def sr_execute(self):
        for c in self.cases:  
            c.setVars(6)

class Case(object):
    def __init__(self, name):
        self.name = name
    def setVars(self, var):
        self.var = var

在我的实验课中,我有一个名为sr_execute的函数。此功能显示所需的行为。我有兴趣解析所有案例并为每个案例设置一个属性。当我运行以下代码时,

if __name__ == '__main__':
    #multiprocessing.freeze_support()
    e = Experiment('exp')
    e.sr_execute()
    for c in e.cases: print c.name, c.var

我明白了,

a 6
b 6
c 6

这是理想的行为。

但是,我想使用多处理并行执行此操作。为此,我向实验类添加了一个mp_execute()函数,

    def mp_execute(self):
        processes = []
        for c in self.cases: 
            processes.append(Process(target= c.setVars, args = (6,)))
        [p.start() for p in processes]
        [p.join() for p in processes]

然而,这不起作用。当我执行以下操作时,

if __name__ == '__main__':
    #multiprocessing.freeze_support()
    e = Experiment('exp')
    e.mp_execute()
    for c in e.cases: print c.name, c.var

我收到错误,

AttributeError: 'Case' object has no attribute 'var'

显然,我无法使用多处理设置class属性。

任何线索,

2 个答案:

答案 0 :(得分:3)

致电时:

def mp_execute(self):
    processes = []
    for c in self.cases: 
        processes.append(Process(target= c.setVars, args = (6,)))
    [p.start() for p in processes]
    [p.join() for p in processes]

当您创建Process时,它将使用您对象的副本,并且对此类对象的修改传递给主程序,因为不同的进程有不同的地址空间。如果您使用Thread,它会起作用 因为在这种情况下没有创建副本。

另请注意,您的代码可能会在Windows中失败,因为您将方法作为target传递,Windows要求target可以选择(实例方法可拾取)。 target应该是在模块顶层定义的函数,以便处理所有Oses。

如果您想与主流程进行通信,您可以进行以下更改:

无论如何,必须通过设置“频道”(如Queue)或设置共享状态来“明确”处理通信。


样式注释:以这种方式使用list-comprehensions:

[p.join() for p in processes]

它只是错误。您只是在浪费空间来创建None的列表。与方式相比,它也可能更慢:

for p in processes:
    p.join()

因为必须将元素附加到列表中。

有人说列表推导比for循环略快,但是:

  • 性能差异很小,通常无关紧要
  • 当且仅当考虑这种循环时,它们才会更快

    a = []
    for element in something:
        a.append(element)
    

    如果循环(如本案例中)创建list,那么for循环会更快。

顺便说一下:有些人以同样的方式使用map来执行副作用。这又是错误的,因为你没有获得太多的速度,原因与之前在python3中完全失败,其中map返回一个迭代器,因此它会完全执行这些功能,从而降低了代码的可移植性。

答案 1 :(得分:1)

@ Bakuriu的回答提供了良好的造型和效率建议。确实,每个进程都获得了主进程堆栈的副本,因此分叉进程所做的更改不会反映在主进程的地址空间中,除非您使用某种形式的IPC(例如Queue,管道,经理)。

但是您遇到的特定AttributeError: 'Case' object has no attribute 'var'错误还有其他原因,即您的Case对象 在您启动时仍具有var属性流程。相反,var属性是在setVars()方法中创建的。

你的分叉进程在调用setVars()时确实创建了变量(实际上甚至将它设置为6),但是,这种变化只发生在Case对象的副本中,即没有反映在主进程的内存空间中(变量仍然不存在)。

要了解我的意思,请将您的Case类更改为:

class Case(object):
    def __init__(self, name):
        self.name = name
        self.var = 7  # Create var in the constructor.
    def setVars(self, var):
        self.var = var

通过在构造函数中添加var成员变量,您的主进程将可以访问它。当然,分叉进程中的更改仍然不会反映在主进程中,但至少不会出现错误:

a 7
b 7
c 7

希望这能够揭示正在发生的事情。 =)


<强> SOLUTION:

最少侵入性(对原始代码)要做的是使用共享内存中的ctypes对象:

from multiprocessing import Value
class Case(object):
    def __init__(self, name):
        self.name = name
        self.var = Value('i', 7)              # Use ctypes "int" from shared memory.
    def setVars(self, var):
        self.var.value = var                  # Set the variable's "value" attribute.

并更改main()以打印c.var.value:

for c in e.cases: print c.name, c.var.value   # Print the "value" attribute.

现在您有了所需的输出:

a 6
b 6
c 6