多进程:共享进程间的大型只读对象?

子程序是通过在程序中创build的多处理共享对象产生的吗?

我有以下设置:

do_some_processing(filename): for line in file(filename): if line.split(',')[0] in big_lookup_object: # something here if __name__ == '__main__': big_lookup_object = marshal.load('file.bin') pool = Pool(processes=4) print pool.map(do_some_processing, glob.glob('*.data')) 

我将一些大对象加载到内存中,然后创build一个需要使用这个大对象的工作者池。 大对象是以只读方式访问的,我不需要在进程之间传递它的修改。

我的问题是:将大对象加载到共享内存中,因为如果我在unix / c中产生了一个进程,或者每个进程都加载了它自己的大对象副本?

更新:进一步澄清 – big_lookup_object是一个共享的查找对象。 我不需要把它分开,分开处理。 我需要保留一份。 我需要拆分它的工作是阅读大量的其他大型文件,并在查找对象的大文件中查找项目。

进一步更新:数据库是一个很好的解决scheme,memcached可能是一个更好的解决scheme,并且磁盘(shelve或dbm)上的文件可能会更好。 在这个问题上,我对内存解决scheme特别感兴趣。 对于最终的解决scheme,我将使用hadoop,但我想看看我是否也可以拥有本地的内存版本。

“通过在程序中创build的多处理共享对象生成subprocess吗?”

没有。

进程有独立的内存空间。

解决scheme1

为了充分利用有大量工人的大型build筑,请做到这一点。

  1. 把每个工作者写成一个“filter” – 从标准input读取中间结果,确实起作用,在标准输出中写入中间结果。

  2. 连接所有的工人作为一个pipe道:

     process1 <source | process2 | process3 | ... | processn >result 

每个进程读取,工作和写入。

这是非常有效的,因为所有进程都在同时运行。 写入和读取直接通过进程之间的共享缓冲区。


解决scheme2

在某些情况下,你有更复杂的结构 – 往往是“扇出”的结构。 在这种情况下,你有一个有多个孩子的父母。

  1. 父母打开源数据。 家长分叉了一些孩子。

  2. 家长读源,农场的一部分源出来给每个同时运行的孩子。

  3. 当父母到达最后时,closurespipe道。 孩子得到文件的结尾,并正常完成。

由于每个孩子只读取sys.stdin ,孩子的部分写起来很愉快。

家长有一点花式的步法产卵所有的孩子,并妥善保留pipe道,但也不是太糟糕。

扇入是相反的结构。 许多独立运行的进程需要将其input交织到一个共同的进程中。 collections家不容易写,因为它必须从许多来源读取。

从许多命名pipe道读取通常使用select模块来查看哪些pipe道有待处理的input。


解决scheme3

共享查找是数据库的定义。

解决scheme3A – 加载数据库。 让工作人员处理数据库中的数据。

解决scheme3B – 使用werkzeug (或类似的)创build一个非常简单的服务器来提供响应HTTP GET的WSGI应用程序,以便工作人员可以查询服务器。


解决scheme4

共享文件系统对象。 Unix操作系统提供共享内存对象。 这些只是映射到内存的文件,以便交换I / O而不是更多的常规缓冲读取。

您可以通过几种方式从Python上下文中执行此操作

  1. 写一个启动程序,(1)将原始巨大的对象分解成更小的对象,(2)启动工作者,每个对象都有一个较小的对象。 较小的对象可以被腌制的Python对象来节省一小部分的文件读取时间。

  2. 编写一个启动程序(1)读取您的原​​始巨大的对象,并使用seek操作编写页面结构的字节编码文件,以确保通过简单的search很容易find各个部分。 这是数据库引擎所做的 – 将数据分解成页面,通过查找使每个页面易于定位。

    产生工人访问这个这个大的页面结构的文件。 每个工人都可以寻求相关的部分,并在那里工作。

子程序是通过在程序中创build的多处理共享对象产生的吗?

这取决于。 对于全局只读variables,通常可以这样认为(除了内存消耗),否则不应该这样做。

多处理的文档说:

Better to inherit than pickle/unpickle

在Windows上,来自多处理的许多types都必须是可挑选的,以便subprocess可以使用它们。 但是,通常应该避免使用pipe道或队列将共享对象发送到其他进程。 相反,您应该安排程序,以便需要访问在别处创build的共享资源的进程可以从祖先进程inheritance它。

Explicitly pass resources to child processes

在Unix上,subprocess可以使用全局资源在父进程中创build的共享资源。 但是,最好将该对象作为parameter passing给subprocess的构造函数。

除了使代码(可能)与Windows兼容外,这也确保了只要subprocess仍然存在,对象将不会被垃圾收集到父进程中。 如果在父进程中垃圾收集对象时释放一些资源,这可能很重要。

Global variables

请记住,如果在subprocess中运行的代码尝试访问全局variables,那么它所看到的值(如果有的话)可能与Process.start()被调用时的父进程中的值不同。

在Windows(单个CPU)上:

 #!/usr/bin/env python import os, sys, time from multiprocessing import Pool x = 23000 # replace `23` due to small integers share representation z = [] # integers are immutable, let's try mutable object def printx(y): global x if y == 3: x = -x z.append(y) print os.getpid(), x, id(x), z, id(z) print y if len(sys.argv) == 2 and sys.argv[1] == "sleep": time.sleep(.1) # should make more apparant the effect if __name__ == '__main__': pool = Pool(processes=4) pool.map(printx, (1,2,3,4)) 

随着sleep

 $ python26 test_share.py sleep 2504 23000 11639492 [1] 10774408 1 2564 23000 11639492 [2] 10774408 2 2504 -23000 11639384 [1, 3] 10774408 3 4084 23000 11639492 [4] 10774408 4 

没有sleep

 $ python26 test_share.py 1148 23000 11639492 [1] 10774408 1 1148 23000 11639492 [1, 2] 10774408 2 1148 -23000 11639324 [1, 2, 3] 10774408 3 1148 -23000 11639324 [1, 2, 3, 4] 10774408 4 

S.Lott是正确的。 Python的多处理快捷方式有效地为您提供了一个单独的,重复的内存块。

在大多数* nix系统中,使用os.fork()的低级调用实际上会给你写时复制的内存,这可能就是你的想法。 在理论上,AFAIK尽可能采用最简单的程序,您可以从数据中读取数据,而不必重复。

然而,在Python解释器中事情并不那么简单。 对象数据和元数据存储在同一个内存段中,所以即使对象不会改变,对象的引用计数器也会增加,这会导致内存写入,从而导致复制。 几乎所有比“打印”hello“”更多的Python程序将会引起引用计数增量,所以你可能永远不会意识到copy-on-write的好处。

即使有人设法在Python中破解共享内存解决scheme,但试图协调跨进程的垃圾收集可能会非常痛苦。

如果你在Unix下运行,由于fork的工作方式 (即subprocess有单独的内存,但是它是写入时拷贝,所以只要没有人修改它就可以共享),它们可以共享同一个对象。 我尝试了以下内容:

 import multiprocessing x = 23 def printx(y): print x, id(x) print y if __name__ == '__main__': pool = multiprocessing.Pool(processes=4) pool.map(printx, (1,2,3,4)) 

并得到以下输出:

 $ ./mtest.py
 23 22995656
 1
 23 22995656
 2
 23 22995656
 3
 23 22995656
 4

当然,这并不能certificate没有拷贝,但是你应该可以通过查看ps的输出来validation你的情况,看看每个subprocess使用了​​多less实际的内存。

不同的进程有不同的地址空间。 就像运行解释器的不同实例一样。 这就是IPC(进程间通信)的用途。

您可以使用队列或pipe道来达到此目的。 如果您想稍后通过networking分发进程,也可以使用rpc over tcp。

http://docs.python.org/dev/library/multiprocessing.html#exchanging-objects-between-processes

不是直接关系到多重处理本身,但从你的例子来看,你似乎可以使用搁置模块或类似的东西。 “big_lookup_object”是否真的需要完全记忆?

对于Linux / Unix / MacOS平台,forkmap是一个快速而肮脏的解决scheme。