Python 如何有效地让多进程进程读取不可变的大数据

Python 如何有效地让多进程进程读取不可变的大数据,python,multiprocessing,Python,Multiprocessing,我想使用多处理来使用多个核心来运行一个过程,该过程对大列表中的元素进行成对比较: data = [...] #when loaded this is > 100MB for i in xrange(len(data)-1): parent = data[i] for j in xrange(i,len(data)): child = data[j] #do something with parent and child 因此,如果我设置了一

我想使用多处理来使用多个核心来运行一个过程,该过程对大列表中的元素进行成对比较:

data = [...] #when loaded this is > 100MB
for i in xrange(len(data)-1):
    parent = data[i]
    for j in xrange(i,len(data)):
        child = data[j]
        #do something with parent and child
因此,如果我设置了一个进程队列:

def worker(queue):
    while True:
        args = queue.get()
        if args == 'EOF':
            break
        f(*args)

def f(data, x, start):
    for i in xrange(start,len(data)):
       #do stuff

if __name__ == '__main__':
    from multiprocessing import Process, Queue, cpu_count
    import psycopg2

    cur = psycopg2.connect(...).cursor()
    data = cur.execute('SELECT * from table') 
    #when loaded into memory data is > 100MB

    other_f_arg = 'some object'

    queue = Queue()
    #spawn 1 child per core:
    workers = [Process(target=worker, args=((queue,)) for cpu in xrange(cpu_count())]
    for w in workers:
        w.start()

    for i in xrange(len(data)-1):
        queue.put((data, other_f_arg, i))

    queue.put('EOF')
    for w in workers:
        w.join()
运行此操作时,queue.put会在每次迭代时将
数据
推入队列,即使每个进程只需读取一次数据,然后再重新引用。因此,重复的数据传递否定了multiproc的所有优势。如何让每个进程只拾取一次
数据
其他参数的副本
,然后在释放工作人员时只传递动态变量
I

更新1:

根据Tim Peters在下面的建议,我决定使用
Pool
,但不是使用
map
,而是使用带有回调的
apply_async
(因为我希望父进程以串行方式对
f
的返回进行一些后处理,而不是等待所有提交完成)(因为
f
也将返回内存中较大的内容):


有没有办法将子进程中未捕获的异常重定向到控制台?(比如单线程进程中引发的异常?)我这样问是因为
f
中的未捕获异常似乎只是打破了调用
apply\u async
的循环,我不会将错误发送到控制台或任何东西。

最简单:在Linux-y系统上(一个支持
fork()
)的操作系统),在模块级定义
数据
。然后所有工作进程都会神奇地看到
数据的副本,这是由于神奇的
fork()
语义

更具可移植性:使用
multiprocessing.Pool()
相反。当您创建
池时,您可以指定要运行的初始化函数,以及要传递给该函数的参数。然后,您可以将
数据
每个进程只传递一次给某个函数,例如,将其绑定到模块全局名称。然后其他函数可以只引用该模块全局。
池()
还支持几种不需要显式管理队列的分发工作(和检索结果)方法。此处不了解足够的详细信息,无法说明这对您的特定问题是好是坏

充实“便携”方式

这里有一种方法:

NUM_CPU = None  # defaults to all available CPUs

def worker_init(xdata, xother_f_arg):
    global data, other_f_arg
    data = xdata
    other_f_arg = xother_f_arg

def f(start):
    for i in xrange(start, len(data)):
       #do stuff

if __name__ == '__main__':
    from multiprocessing import Pool
    import psycopg2

    cur = psycopg2.connect(...).cursor()
    data = cur.execute('SELECT * from table') 
    other_f_arg = 'some object'

    pool = Pool(processes=NUM_CPU,
                initializer=worker_init,
                initargs=(data, other_f_arg))
    pool.map(f, xrange(len(data) - 1))
    pool.close()
    pool.join()
请注意,它的代码也比抛出自己的队列少得多

虽然我不能肯定地运行您的代码,但我希望您最好不要使用
多处理
机器传递庞大的
数据
,而是让每个工作人员从数据库加载自己的副本。大致如下:

def worker_init(xother_f_arg):
    import psycopg2
    global data, other_f_arg
    other_f_arg = xother_f_arg
    cur = psycopg2.connect(...).cursor()
    data = cur.execute('SELECT * from table') 
编辑-处理错误

并行手段很难在子进程(或线程)中引发异常,因为它们发生在通常与主程序当时正在执行的操作无关的上下文中。处理这一问题的最简单方法是保留对正在创建的
AsyncResult
对象的引用,并显式地从它们中获取
.get()
结果(失去回拨功能!这只是一个无用的复杂问题)。替换您的:

for i in xrange(len(data)-1):
    pool.apply_async(f,
                     args=(i,),
                     callback=shim_callback)
例如

# queue up all the work
futures = [pool.apply_async(f, args=(i,))
           for i in xrange(len(data) - 1)]
# retrieve results
for fut in futures:
    try:
        result = fut.get()
    except NameExceptionsYouWantToCatchHere as e:
        # do whatever you want with the exception
    else:
        # process result
从文档(当前的Python 2)中:

获取([超时])

当结果到达时返回结果。如果timeout不是None,则 结果未在超时秒内到达,则引发multiprocessing.TimeoutError。如果远程调用引发异常,则get()将重新引发该异常

在Python3中,还有一个
map\u async()
方法,以及许多
Pool()
方法上的可选
error\u回调
参数

注:如果
len(数据)
i
它非常大,
多处理
机器可以消耗相应的大量RAM来排队所有工作项-
apply\u async()
从不阻塞,循环会尽可能快地将工作项排队。在这种情况下,可能需要另一层缓冲。

问题是,将“数据”传递给工作人员(=进程)会使数据被复制。因为这是一个相当大的数据集,所以你不会(即使你可以检查确认)有任何速度提升

根据您拥有的数据类型,您应该检查多处理阵列。它可能比“全局”更安全

您可以使用的代码类型是:

from multiprocessing import Process, Queue, cpu_count
import psycopg2
cur = psycopg2.connect(...).cursor()
data = cur.execute('SELECT * from table') 
#when loaded into memory data is > 100MB
shared_array = Array('your_data_type', data)

def worker(queue):
    while True:
       args = queue.get()
       if args == 'EOF':
           break
       f(*args)

def f(data, x, start):
    for i in xrange(start,len(data)):
      shared array[!!!!]#do stuff

if __name__ == '__main__':
    other_f_arg = 'some object'

    queue = Queue()
    #spawn 1 child per core:
    workers = [Process(target=worker, args=((queue,)) for cpu in xrange(cpu_count())]
    for w in workers:
        w.start()

    for i in xrange(len(data)-1):
        queue.put((data, other_f_arg, i))

    queue.put('EOF')
    for w in workers:
        w.join()

我的数组是一个dict数组,所以除非数组可以从中自动构建一个cstruct…而且,当工作人员使用它时,数组应该是只读/静态的,所以我把它设为全局数组没有问题,我只需要在主进程生成其内容后让工作人员可以访问它…我正在做一系列的工作sql查询结果的后处理,因此,如果您在使用
fork()的系统上运行,我将只执行一次,然后推动数据,而不是让每个工作人员都复制它
,您真的不必到处推送数据。在创建任何辅助进程之前,在主程序中的模块级构建一次
数据
。辅助进程是通过
fork()
(在具有
fork()
的系统上)创建的,因此它们可以在
fork()时神奇地获得主程序中所有内容的副本调用了
。我改用了apply_async(请参阅更新的OP),效果很好。每个进程只加载一次
数据
。问题:有没有办法将在子进程下运行的
f
中未捕获的异常重定向到控制台(就像未捕获的异常会被发送到stderr一样(或windows上的任何等价物)正常情况下)?现在,
f
中的运行时异常会导致池终止,并且没有输出。
from multiprocessing import Process, Queue, cpu_count
import psycopg2
cur = psycopg2.connect(...).cursor()
data = cur.execute('SELECT * from table') 
#when loaded into memory data is > 100MB
shared_array = Array('your_data_type', data)

def worker(queue):
    while True:
       args = queue.get()
       if args == 'EOF':
           break
       f(*args)

def f(data, x, start):
    for i in xrange(start,len(data)):
      shared array[!!!!]#do stuff

if __name__ == '__main__':
    other_f_arg = 'some object'

    queue = Queue()
    #spawn 1 child per core:
    workers = [Process(target=worker, args=((queue,)) for cpu in xrange(cpu_count())]
    for w in workers:
        w.start()

    for i in xrange(len(data)-1):
        queue.put((data, other_f_arg, i))

    queue.put('EOF')
    for w in workers:
        w.join()