多处理池是否为每个进程提供相同数量的任务,或者它们是否可用?

时间:2012-11-07 06:41:49

标签: python multiprocessing pool

map迭代到multiprocessing.Pool时,迭代被划分为开始时池中每个进程的队列,或者是否存在从中获取任务的公共队列过程是免费的吗?

    def generate_stuff():
        for foo in range(100):
             yield foo

    def process(moo):
        print moo

    pool = multiprocessing.Pool()
    pool.map(func=process, iterable=generate_stuff())
    pool.close()

所以给出了这个未经测试的建议代码;如果池中有4个进程,每个进程都会分配25个要做的事情,或者100个进程被进程逐个挑选,寻找要做的事情,以便每个进程可以执行不同数量的东西,例如30 ,26,24,20。

2 个答案:

答案 0 :(得分:23)

  

所以给出了这个未经测试的建议代码;如果池中有4个进程,每个进程都会分配25个要做的事情,或者100个进程被进程逐个挑选,寻找要做的事情,以便每个进程可以执行不同数量的东西,例如30 ,26,24,20。

嗯,显而易见的答案是测试它。

按原样,测试可能不会告诉你太多,因为工作将尽快完成,即使池化进程在准备就绪时抓住了工作,事情也可能最终均匀分布。但有一种简单的方法可以解决这个问题:

import collections
import multiprocessing
import os
import random
import time

def generate_stuff():
    for foo in range(100):
        yield foo

def process(moo):
    #print moo
    time.sleep(random.randint(0, 50) / 10.)
    return os.getpid()

pool = multiprocessing.Pool()
pids = pool.map(func=process, iterable=generate_stuff(), chunksize=1)
pool.close()
print collections.Counter(pids)

如果这些数字是“锯齿状”,你就知道合并的流程必须抓住新的工作就绪。 (我明确地将chunksize设置为1以确保块不是那么大,以至于每个块只获得一个块。)

当我在8核机器上运行时:

Counter({98935: 16, 98936: 16, 98939: 13, 98937: 12, 98942: 12, 98938: 11, 98940: 11, 98941: 9})

因此,看起来流程正在快速获得新工作。

由于您专门询问了4名工人,因此我将Pool()更改为Pool(4)并获得了此信息:

Counter({98965: 31, 98962: 24, 98964: 23, 98963: 22})

但是,有一种更好的方法可以找到而不是通过测试:阅读the source

如您所见,map只调用map_async,它会创建一堆批次并将它们放在self._taskqueue对象(Queue.Queue实例)上。如果你进一步阅读,这个队列不会直接与其他进程共享,但是有一个池管理器线程,只要进程完成并返回结果,就会从队列中弹出下一个作业并将其提交回进程。 / p>

这也是你如何找出map的默认chunksize的方法。上面链接的2.7实现显示它只是len(iterable) / (len(self._pool) * 4)四舍五入(比避免分数算术稍微冗长一点) - 或者换句话说,只是大到足以容纳每个进程大约4个块。但你真的不应该依赖于此;文档模糊地和间接地暗示它将使用某种启发式方法,但不会给你任何关于它将是什么的保证。因此,如果您确实需要“每个进程大约4个块”,请明确计算它。更现实的是,如果您需要除默认值之外的任何内容,您可能需要一个特定于域的值(通过计算,猜测或分析)。

答案 1 :(得分:1)

要估算Python实现使用的chunksize而不查看其multiprocessing模块源代码,请运行:

#!/usr/bin/env python
import multiprocessing as mp
from itertools import groupby

def work(index):
    mp.get_logger().info(index)
    return index, mp.current_process().name

if __name__ == "__main__":
    import logging
    import sys
    logger = mp.log_to_stderr()

    # process cmdline args
    try:
        sys.argv.remove('--verbose')
    except ValueError:
        pass  # not verbose
    else:
        logger.setLevel(logging.INFO)  # verbose
    nprocesses, nitems = int(sys.argv.pop(1)), int(sys.argv.pop(1))
    # choices: 'map', 'imap', 'imap_unordered'
    map_name = sys.argv[1] if len(sys.argv) > 1 else 'map'
    kwargs = dict(chunksize=int(sys.argv[2])) if len(sys.argv) > 2 else {}

    # estimate chunksize used
    max_chunksize = 0
    map_func = getattr(mp.Pool(nprocesses), map_name)
    for _, group in groupby(sorted(map_func(work, range(nitems), **kwargs),
                                   key=lambda x: x[0]),  # sort by index
                            key=lambda x: x[1]):  # group by process name
        max_chunksize = max(max_chunksize, len(list(group)))
    print("%s: max_chunksize %d" % (map_name, max_chunksize))

它显示imapimap_unordered默认使用chunksize=1max_chunksize使用map取决于nprocessesnitem (每个进程的块数不固定),max_chunksize取决于python版本。如果已指定,则所有*map*函数都会考虑chunksize参数。

用法

$ ./estimate_chunksize.py nprocesses nitems [map_name [chunksize]] [--verbose]

了解个别工作的分配方式;指定--verbose参数。