Python Multiprocessing.Pool延迟迭代

时间:2011-03-15 22:39:07

标签: python multiprocessing

我想知道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'。

有人可以清理正在发生的事情吗?如果有某种方法让进程池'懒惰'根据需要评估迭代器?

谢谢,

加布

4 个答案:

答案 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()