[英]Python Multiprocessing.Pool lazy iteration
我想知道 python 的 Multiprocessing.Pool class 与 map、imap 和 map_async 一起工作的方式。 我的特殊问题是我想要 map 在创建内存密集型对象的迭代器上,并且不希望所有这些对象同时生成到 memory 中。 我想看看各种 map() 函数是否会让我的迭代器干涸,或者仅在子进程缓慢推进时智能地调用 next() function,所以我破解了一些测试:
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'。
有人可以弄清楚发生了什么,是否有某种方法可以让进程池“懒惰地”根据需要评估迭代器?
谢谢,
加贝
我们先来看看程序的结尾。
multiprocessing 模块使用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
S' inqueue
在Pool._handle_tasks
功能。
最重要的是,所有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)
我也有这个问题,并且很失望地得知 map 消耗了它的所有元素。 我编写了一个函数,该函数在多处理中使用 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。 一名工人不能阻止其他人取得进步。 天啊。 请注意,您可能希望使用不同的对象来为工作人员发出终止信号。 在示例中,我使用了 None。
在“Python 2.7 (r27:82525, Jul 4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32”上测试
你想要的是在NuMap包中实现的,来自网站:
NuMap 是一个并行(基于线程或进程,本地或远程)、缓冲、多任务、itertools.imap 或 multiprocessing.Pool.imap 函数替换。 像 imap 一样,它对序列或可迭代元素的函数求值,而且它是惰性的。 懒惰可以通过“stride”和“buffer”参数进行调整。
在这个例子中(请参阅代码)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()
我也遇到了这个问题,并得出了与此处其他答案不同的解决方案,所以我想我会分享它。
import collections, multiprocessing
def map_prefetch(func, data, lookahead=128, workers=16, timeout=10):
with multiprocessing.Pool(workers) as pool:
q = collections.deque()
for x in data:
q.append(pool.apply_async(func, (x,)))
if len(q) >= lookahead:
yield q.popleft().get(timeout=timeout)
while len(q):
yield q.popleft().get(timeout=timeout)
for x in map_prefetch(myfunction, huge_data_iterator):
# do stuff with x
基本上是使用队列将最多lookahead
挂起的请求发送到工作池,从而对缓冲结果实施限制。 工作在该限制内尽快开始,因此它可以并行运行。 结果也保持秩序。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.