繁体   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