多处理:在进程之间共享一个大的只读对象?

时间:2009-03-18 19:58:46

标签: python multiprocessing

通过程序中先前创建的multiprocessing共享对象生成子进程吗?

我有以下设置:

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'))

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

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

更新:进一步澄清 - big_lookup_object是一个共享查找对象。我不需要拆分它并单独处理它。我需要保留一份副本。我需要分割它的工作是读取许多其他大文件,并在查找对象中查找这些大文件中的项目。

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

8 个答案:

答案 0 :(得分:46)

“通过程序中先前创建的多处理共享对象生成子进程吗?”

没有。

进程具有独立的内存空间。

解决方案1 ​​

为了充分利用拥有大量工人的大型结构,请执行此操作。

  1. 将每个工作者写为“过滤器” - 从stdin读取中间结果,确实有效,在stdout上写入中间结果。

  2. 将所有工人连接为管道:

    process1 <source | process2 | process3 | ... | processn >result
    
  3. 每个流程都会读取,工作和写入。

    由于所有进程同时运行,因此效率非常高。写入和读取直接通过进程之间的共享缓冲区传递。


    解决方案2

    在某些情况下,您的结构更复杂 - 通常是“扇出”结构。在这种情况下,您的父母有多个孩子。

    1. Parent打开源数据。父母要求一些孩子。

    2. Parent读取源代码,将源代码的各个部分存储到每个并发运行的子代中。

    3. 当父级到达末尾时,关闭管道。孩子得到文件结束并正常结束。

    4. 儿童部分写得愉快,因为每个孩子只需阅读sys.stdin

      父母在产生所有孩子并正确保留管道方面有一些花哨的步法,但这并不算太差。

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

      通常使用select模块从许多命名管道中读取,以查看哪些管道具有待处理的输入。


      解决方案3

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

      解决方案3A - 加载数据库。让工人处理数据库中的数据。

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


      解决方案4

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

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

      1. 编写一个启动程序,(1)将原始的巨大对象分解为较小的对象,(2)启动worker,每个worker都有一个较小的对象。较小的对象可以是pickle Python对象,以节省一点文件读取时间。

      2. 编写一个启动程序,该程序(1)读取原始的巨大对象,并使用seek操作编写页面结构的字节编码文件,以确保通过简单的搜索很容易找到各个部分。这就是数据库引擎的作用 - 将数据分解为页面,通过seek轻松找到每个页面。

        Spawn worker可以访问这个大型页面结构文件。每个工人都可以寻求相关部分并在那里开展工作。

答案 1 :(得分:35)

通过程序中先前创建的多处理共享对象生成子进程吗?

这取决于。对于全局只读变量,通常可以这样考虑(除了消耗的内存),否则它不应该这样。

multiprocessing的文档说:

Better to inherit than pickle/unpickle

  

在Windows上有很多类型   多处理需要可选择   以便子进程可以使用它们。   但是,人们通常应该避免   将共享对象发送给其他人   使用管道或队列的进程。   相反,你应该安排该计划   这样一个需要访问的进程   在别处创建的共享资源   可以从祖先继承它   过程

Explicitly pass resources to child processes

  

在Unix上,子进程可以使用   一个共享资源的创建   使用全局的父进程   资源。但是,它更好   将对象作为参数传递给   子进程的构造函数。

     

除了制作代码   (可能)与Windows兼容   这也确保了只要   孩子的过程还活着   对象不会被垃圾收集   在父进程中。这可能是   一些资源被释放是很重要的   当对象被垃圾收集时   在父进程中。

Global 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

答案 2 :(得分:26)

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

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

但是,Python解释器中的事情并不那么简单。对象数据和元数据存储在同一个内存段中,因此即使对象永远不会发生变化,类似于该对象的引用计数器递增也会导致内存写入,从而导致复制。几乎所有不仅仅是“打印'hello'”的Python程序都会导致引用计数增量,因此您可能永远不会意识到写入时复制的好处。

即使有人设法破解了Python中的共享内存解决方案,尝试协调跨进程的垃圾收集也可能会非常痛苦。

答案 3 :(得分:6)

如果您在Unix下运行,由于how fork works,它们可能共享同一个对象(即,子进程具有单独的内存但是它是写时复制的,因此它可以共享,只要它可以共享没有人修改它)。我尝试了以下方法:

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

当然,这并不是证明尚未制作副本,但您应该能够通过查看ps的输出来验证您的情况每个子进程使用多少实内存。

答案 4 :(得分:2)

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

您可以使用队列或管道来实现此目的。如果要稍后通过网络分发进程,也可以使用rcp通过tcp。

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

答案 5 :(得分:1)

与多处理本身并不直接相关,但从您的示例来看,您似乎可以使用shelve模块或类似的东西。 “big_lookup_object”真的必须完全记忆吗?

答案 6 :(得分:1)

否,但是您可以将数据作为子进程加载,并允许它与其他子进程共享数据。见下文。

import time
import multiprocessing

def load_data( queue_load, n_processes )

    ... load data here into some_variable

    """
    Store multiple copies of the data into
    the data queue. There needs to be enough
    copies available for each process to access. 
    """

    for i in range(n_processes):
        queue_load.put(some_variable)


def work_with_data( queue_data, queue_load ):

    # Wait for load_data() to complete
    while queue_load.empty():
        time.sleep(1)

    some_variable = queue_load.get()

    """
    ! Tuples can also be used here
    if you have multiple data files
    you wish to keep seperate.  
    a,b = queue_load.get()
    """

    ... do some stuff, resulting in new_data

    # store it in the queue
    queue_data.put(new_data)


def start_multiprocess():

    n_processes = 5

    processes = []
    stored_data = []

    # Create two Queues
    queue_load = multiprocessing.Queue()
    queue_data = multiprocessing.Queue()

    for i in range(n_processes):

        if i == 0:
            # Your big data file will be loaded here...
            p = multiprocessing.Process(target = load_data,
            args=(queue_load, n_processes))

            processes.append(p)
            p.start()   

        # ... and then it will be used here with each process
        p = multiprocessing.Process(target = work_with_data,
        args=(queue_data, queue_load))

        processes.append(p)
        p.start()

    for i in range(n_processes)
        new_data = queue_data.get()
        stored_data.append(new_data)    

    for p in processes:
        p.join()
    print(processes)    

答案 7 :(得分:-4)

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