我想使用多处理来使用多个核来运行一个程序,该程序对大型列表中的元素进行成对比较:
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会在每次迭代时将data
推送到队列中,即使数据只需要读取一次,然后只需重新引用每个进程。因此,重复数据传递抵消了multiproc的所有优点。如何让每个流程只需一次获取data
和other_f_arg
的副本,然后在工作人员被释放时只传递动态变量i
?
更新1:
我决定按照Tim Peters的建议使用Pool
,但不是使用map
,而是使用apply_async
进行回调(因为我希望父进程执行一些操作)以串行方式对f
的返回进行后处理,而不是等待所有提交完成(因为f
也会在内存中返回大的内容):
def worker_init(xdata):
global data
data = xdata
def callback(result, x):
#do something with result of f(i), and x
def f(i):
#do something with data[i]
return result
if __name__ == '__main__':
...
data = psycopg2_cursor.fetchall()
NUM_CPU = None
from multiprocessing import Pool
from functools import partial
pool = Pool(processes=NUM_CPU,
initializer=worker_init,
initargs=(data,))
x = 'some extra param I want to pass to callback'
shim_callback = partial(callback, x=x)
for i in xrange(len(data)-1):
pool.apply_async(f,
args=(i,),
callback=shim_callback)
pool.close()
pool.join()
有没有办法将子节点中未捕获的异常重定向到控制台? (就像在单线程进程中引发的异常一样?)我问,因为f
中未被捕获的异常似乎只是打破了调用apply_async
的循环,并且我没有错误地输出到控制台或任何东西。
答案 0 :(得分:2)
最简单:在Linux-y系统(支持fork()
的操作系统)上,在模块级别定义data
。然后,由于神奇的data
语义,所有工作进程都会神奇地看到fork()
的副本。
更具便携性:改为使用multiprocessing.Pool()
。创建Pool
时,可以指定要运行的初始化函数,以及传递给该函数的参数。然后,您可以在每个进程中仅将data
传递给某个函数,例如,将其绑定到模块全局名称。然后,其他函数可以仅引用该模块全局。 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()
请注意,它的代码也远远少于吊销您自己的队列。
虽然我无法运行您的代码,但我希望您最好不要使用data
机器传递巨大的multiprocessing
而不是 worker从数据库加载自己的副本。沿着:
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
来自docs(当前的Python 2):
get([timeout])
结果到达时返回结果。如果超时不是None而且 结果不会在超时秒内到达,然后引发multiprocessing.TimeoutError。如果远程调用引发了异常,那么get()将重新启动该异常。
在Python 3中,还有一个map_async()
方法,以及许多error_callback
方法的可选Pool()
参数。
注意:如果len(data)
我
非常大,multiprocessing
机器可以消耗相当大量的RAM来排队所有工作项 - apply_async()
永远不会阻塞,并且循环尽可能快地排队工作项。在这种情况下,可能需要另一层缓冲。
答案 1 :(得分:1)
问题是将“数据”传递给工作人员(=进程)会使数据被复制。因为它是一个相当大的数据集,你不会(即使你可以检查确认)有任何速度提升。
根据您拥有的数据类型,您应该检查多处理阵列http://docs.python.org/2/library/multiprocessing.html#multiprocessing.Array。它可能比'全球'更安全
您可以使用的代码类型是:
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()