[英]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.