簡體   English   中英

Python Multiprocessing.Pool 惰性迭代

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM