我想知道python的Multiprocessing.Pool类使用map,imap和map_async的方式。我的特殊问题是我想在一个迭代器上映射,该迭代器创建了占用大量内存的对象,并且不希望所有这些对象同时生成到内存中。我想看看各种map()函数是否会使我的迭代器变干,或者只是在子进程缓慢前进时才智能地调用next()函数,所以我这样修改了一些测试:
def g():
for el in xrange(100):
print el
yield el
def f(x):
time.sleep(1)
return x*x
if __name__ == '__main__':
pool = Pool(processes=4) # start 4 worker processes
go = g()
g2 = pool.imap(f, go)
g2.next()
依旧使用map,imap和map_async。这是最公然的例子,因为简单地在g2上单次调用next()会从我的生成器g()中打印出所有元素,而如果imap这样做'懒惰',我希望它只调用go.next ()一次,因此只打印'1'。
有人可以清理正在发生的事情吗?如果有某种方法让进程池'懒惰'根据需要评估迭代器?
谢谢,
加布
答案 0 :(得分:32)
让我们先看一下程序的结尾。
当程序结束时,多处理模块使用atexit
来调用multiprocessing.util._exit_function
。
如果您删除g2.next()
,您的程序会很快结束。
_exit_function
最终会调用Pool._terminate_pool
。主线程将pool._task_handler._state
的状态从RUN
更改为TERMINATE
。同时pool._task_handler
线程在Pool._handle_tasks
中循环,并在达到条件时退出
if thread._state:
debug('task handler found thread._state != RUN')
break
(参见/usr/lib/python2.6/multiprocessing/pool.py)
这就是阻止任务处理程序完全使用您的生成器g()
的原因。如果您查看Pool._handle_tasks
,您会看到
for i, task in enumerate(taskseq):
...
try:
put(task)
except IOError:
debug('could not put task on queue')
break
这是使用您的发电机的代码。 (taskseq
并不完全是您的生成器,但消耗taskseq
时,您的生成器也是如此。)
相反,当您调用g2.next()
时,主线程调用IMapIterator.next
,并在其到达self._cond.wait(timeout)
时等待。
主线程正在等待而不是
调用_exit_function
是允许任务处理程序线程正常运行的,这意味着完全使用生成器put
'worker
inqueue
中的任务Pool._handle_tasks
1}}功能。
底线是所有Pool
映射函数都使用它给出的整个迭代。如果你想以块的形式使用生成器,你可以这样做:
import multiprocessing as mp
import itertools
import time
def g():
for el in xrange(50):
print el
yield el
def f(x):
time.sleep(1)
return x * x
if __name__ == '__main__':
pool = mp.Pool(processes=4) # start 4 worker processes
go = g()
result = []
N = 11
while True:
g2 = pool.map(f, itertools.islice(go, N))
if g2:
result.extend(g2)
time.sleep(1)
else:
break
print(result)
答案 1 :(得分:4)
我也有这个问题,并且对于了解地图消耗其所有元素感到失望。我编写了一个函数,它在多处理中使用Queue数据类型懒惰地使用迭代器。这类似于@unutbu在对他的回答的评论中所描述的,但正如他所指出的那样,没有重新加载队列的回调机制。相反,Queue数据类型公开了一个超时参数,我使用了100毫秒才能产生良好的效果。
from multiprocessing import Process, Queue, cpu_count
from Queue import Full as QueueFull
from Queue import Empty as QueueEmpty
def worker(recvq, sendq):
for func, args in iter(recvq.get, None):
result = func(*args)
sendq.put(result)
def pool_imap_unordered(function, iterable, procs=cpu_count()):
# Create queues for sending/receiving items from iterable.
sendq = Queue(procs)
recvq = Queue()
# Start worker processes.
for rpt in xrange(procs):
Process(target=worker, args=(sendq, recvq)).start()
# Iterate iterable and communicate with worker processes.
send_len = 0
recv_len = 0
itr = iter(iterable)
try:
value = itr.next()
while True:
try:
sendq.put((function, value), True, 0.1)
send_len += 1
value = itr.next()
except QueueFull:
while True:
try:
result = recvq.get(False)
recv_len += 1
yield result
except QueueEmpty:
break
except StopIteration:
pass
# Collect all remaining results.
while recv_len < send_len:
result = recvq.get()
recv_len += 1
yield result
# Terminate worker processes.
for rpt in xrange(procs):
sendq.put(None)
此解决方案的优点是不会将请求批处理到Pool.map。一个人不能阻止他人取得进步。因人而异。请注意,您可能希望使用其他对象来表示工作人员的终止。在这个例子中,我使用了无。
在win32上测试了“Python 2.7(r27:82525,2010年7月4日,09:01:59)[MSC v.1500 32位(英特尔)]”
答案 2 :(得分:4)
您想要的是在NuMap包中实现的,来自网站:
NuMap是并行的(基于线程或进程的,本地的或远程的), 缓冲,多任务,itertools.imap或multiprocessing.Pool.imap 功能替换。像imap一样,它会对元素的元素进行求值 一个序列或可迭代的,它懒得做。 懒惰可以通过“步幅”和“缓冲”参数进行调整。
答案 3 :(得分:1)
在此示例中(请参阅代码)2个工人。
按预期方式进行工作:当工作人员有空时,进行下一次迭代。
此代码作为主题代码,唯一不同的是:参数大小= 64 k。
64 k-默认套接字缓冲区大小。
import itertools
from multiprocessing import Pool
from time import sleep
def f( x ):
print( "f()" )
sleep( 3 )
return x
def get_reader():
for x in range( 10 ):
print( "readed: ", x )
value = " " * 1024 * 64 # 64k
yield value
if __name__ == '__main__':
p = Pool( processes=2 )
data = p.imap( f, get_reader() )
p.close()
p.join()