Python多处理创建新的内存位置

时间:2016-12-18 16:57:01

标签: python multiprocessing python-multiprocessing

我遇到了python的multiprocessing包的问题。我尝试使用以下示例尽可能简化事情。我们有2个存储库。每个人都有自己的"独立的"记忆(mem),dict。他们还可以访问共享内存(shared_mem),这是mp.Array。在计算之后,mem s填充3个条目,shared_mem应该等于两个单独的mem的入口乘积。我们希望每个mem{0: 0, 1: 1, 2: 2}shared_mem[0, 1, 4]

import multiprocessing as mp

class MemBank(object):
    def __init__(self, shared_mem):
        self.mem = dict()
        self.shared_mem = shared_mem

    def fill_mem(self, n):
        print "\tfilling: id, shared_id = %s %s" % (id(self.mem), id(self.shared_mem))
        for i in xrange(n):
            self.mem[i] = i
            self.shared_mem[i] *= self.mem[i]
        print "\tmem = "+str(self.mem)
        print "\tshared_mem = "+str([elt for elt in self.shared_mem])

if __name__ == "__main__":

    P = 2
    n = 3

    # initialize memory banks
    mem_bank = dict()
    shared_mem = mp.Array('f', [1,1,1], lock=True)
    print "shared_id =", id(shared_mem)
    for p in xrange(P):
        mem_bank[p] = MemBank(shared_mem)
        print "p, id, shared_id =", p, id(mem_bank[p].mem), id(mem_bank[p].shared_mem)

    # fill memory banks in parallel
    processes = [mp.Process(target=mem_bank[p].fill_mem, args=(3,)) for p in xrange(P)]
    for process in processes:
        process.start()
    for process in processes:
        process.join()

    # view the results
    for p in xrange(P):
        bank_p = mem_bank[p]
        print "p, id, shared_id =", p, id(bank_p.mem), id(bank_p.shared_mem)
        print "\tmem =", bank_p.mem
        print "\tshared_mem =", [elt for elt in bank_p.shared_mem]

我正在使用Windows,上面的代码位于python包的一部分模块中。要运行它,请从命令行导航到包目录,然后执行python -m path.to.module。结果:

C:\path\to\package>python -m package.path.to.module
shared_id = 39625840
p, id, shared_id = 0 39624544 39625840
p, id, shared_id = 1 39649328 39625840
        filling: id, shared_id = 38894304 39278672
        mem = {0: 0, 1: 1, 2: 2}
        shared_mem = [0.0, 1.0, 2.0]
        filling: id, shared_id = 39942016 40319056
        mem = {0: 0, 1: 1, 2: 2}
        shared_mem = [0.0, 1.0, 4.0]
p, id, shared_id = 0 39624544 39625840
        mem = {}        
        shared_mem = [0.0, 1.0, 4.0]
p, id, shared_id = 1 39649328 39625840
        mem = {}        
        shared_mem = [0.0, 1.0, 4.0]

我的问题:正如我们从打印输出中看到的那样,我使用mem并行地填充了两个shared_mem并计算multiprocessing(这是fill_mem方法) 。在我尝试查看mem后期计算之前,一切都很好。它们显示为空,即使它们在计算期间被填充并且shared_mem具有正确的结果。 mem属性不需要共享,我希望在计算后恢复它们,而不会让它们共享。

1 个答案:

答案 0 :(得分:2)

  

在我尝试查看mem后期计算之前,一切都很好。它们显示为空,即使它们在计算期间被填满......

啊,但他们不是

multiprocessing视为将自己 1 复制给其他人,然后指导现在是独立人物的近克隆人做事的能力。我们从Jon(原文)开始,制作两份:Jon0(processes[0])和Jon1。我们还为每个克隆提供args=的副本(不是原始的)和target=的副本(不是原始的),尽管通常这并不重要。 2

Jon0运行 3 并执行某些操作,包含一些私有数据(没有其他人看到此内容),以及共享内容。每当他想要读取或写入共享的东西时,他会在必要时等待锁定,获取锁定,读取或写入,然后释放锁定。当他完成后,他消失在一团烟雾中,只留下退出代码。 4

Jon1同样离开并做了一些事情(几乎是同样的事情)并最终在一团烟雾中消失。

原作" us",又名Jon,现在等待Jon0和Jon1噗。然后我们继续打印mem_bank[0]mem_bank[1]。它们完全没有变化,这本身就不足为奇了。令人惊讶的 mem_bank[0].mem[key]mem_bank[1].mem[key]也没有改变......但是现在它不那么令人惊讶了,因为我们给了Jon0一个副本mem_bank[0].mem的em>。他修改了他的副本,而不是我们的原件。同样,Jon1修改了他的mem_bank[1].mem副本,而不是我们原来的副本。

我们看到任何更改的唯一地方是shared_mem,因为它具有特殊的共享类型,并具有相关的锁定。 shared_mem mp.Array对象上的操作进入共享保险库,花哨"等待锁定,锁定,进入共享保险库并执行操作,然后放回锁#34;舞蹈。这包括我们(原来的Jon)自己的共享对象的阅读,即使在Jon0和Jon1已经愚弄之后也是如此。当然,既然没有其他人拿锁,我们总是立刻得到它,但是访问这些东西仍然需要时间成本。

请注意multiprocessingthreading提供了类似的创建和连接方法。它们之间的一个关键区别是线程根本不能创建完全独立的克隆,而是一种联合双胞胎:一个独立的大脑,它居住在同一个身体中。 (唉,由于cpython Global Interpreter Lock,许多看似独立的操作可以在多个CPU上并行完成,但最终会出现单线程,尽管有线程"线程化。#)差异主要存在,因为只要你有独立的克隆,你就会发现他们需要共享的通信渠道,这样他们就可以互相交流。连体双胞胎,分享一个身体,不需要:一个人可以把东西存放在手中然后去睡觉,唤醒另一个大脑然后可以看着它的手。

1 Windows Python和Linux / Unix Python之间的关键区别在于这种复制到克隆的方式。在类Unix系统上,克隆通过os.fork发生,复制任何"你"到目前为止:此时你知道的所有事情被复制。但是,在Windows上,克隆通过生成一个新的(空)Python实例,运行相同的程序,但在多处理代码中启动运行在某处 5 的副本,而__name__没有'__main__'

2 副本通过pickle模块完成,将Python类和数据对象转换为字符串。 pickle失败时,复制过程非常重要!有关如何与克隆交换字符串的详细信息通常完全不相关,在Unix和Windows上有所不同。

3 这"跑掉"当您致电start或之后不久,就会发生这种情况。在此之前不会创建克隆本身,并且一旦创建了克隆本身,它就会读取腌制的targetargskwargs。然后它调用self.run,其整个代码为:

    if self._target:
        self._target(*self._args, **self._kwargs)

这意味着您可以创建一个Process的子类,它不会使用self._target和两个参数,让您不必费心通过任何内容。这没有什么真正的意义虽然:它只是通过这种方式来保持与Thread类的对称性。

4 退出代码通过操作系统传回,因此仅限于操作系统提供的任何内容。

5 在Windows上管理它的方式特别棘手。本质上,原始进程使用命令行参数生成一个新的Python命令:

    if getattr(sys, 'frozen', False):
        return [sys.executable, '--multiprocessing-fork']
    else:
        prog = 'from multiprocessing.forking import main; main()'
        opts = util._args_from_interpreter_flags()
        return [_python_exe] + opts + ['-c', prog, '--multiprocessing-fork']

这个复杂的方法允许multiprocessing模块(至少在大多数情况下)检测无法使用正确编程习惯用法的代码(既防止无限递归,又调用特殊的多处理冻结支持代码)如果需要,请在__main__部分。)

在类似Unix的系统上要容易得多,multiprocessing可以调用os.fork来制作即将从os.fork调用返回的克隆。新克隆知道他是克隆人,因为他的 os.fork调用返回0,而原始人知道他是原始克隆,因为他的 { {1}}调用返回克隆的ID。