Python 多处理池是否为每个进程提供相同数量的任务,或者是否将其分配为可用任务?

Python 多处理池是否为每个进程提供相同数量的任务,或者是否将其分配为可用任务?,python,multiprocessing,pool,Python,Multiprocessing,Pool,当您map将一个iterable映射为一个多处理池时,迭代是否在开始时为池中的每个进程划分为一个队列,或者在进程空闲时是否有一个公共队列从中执行任务 def generate_stuff(): for foo in range(100): yield foo def process(moo): print moo pool = multiprocessing.Pool() pool.map(func=p

当您
map
将一个iterable映射为一个
多处理池时,迭代是否在开始时为池中的每个进程划分为一个队列,或者在进程空闲时是否有一个公共队列从中执行任务

    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。

map(func,iterable[,chunksize])

此方法将iterable分割为若干个块,以供使用 作为单独的任务提交到流程池。(近似)尺寸 可以通过将chunksize设置为正值来指定这些块的大小 整数

我假设一个进程在处理前一个块时从队列中拾取下一个块

默认的
chunksize
取决于
iterable
的长度,并且选择该值时,块的数量大约是进程数量的四倍

因此,考虑到这个未经测试的建议代码;如果池中有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})
然而,有一种比测试更好的方法来找出答案:阅读

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


这也是如何找出
map
的默认chunksize的方法。上面链接的2.7实现表明,它只是
len(iterable)/(len(self.\u pool)*4)
四舍五入(为了避免分数算术,比这稍微详细一点)-或者,换句话说,刚好足够每个进程处理大约4个块。但你真的不应该依赖这个;文档含糊不清地间接暗示它将使用某种启发式方法,但没有为您提供任何关于这将是什么的保证。因此,如果您确实需要“每个进程大约4个块”,请显式计算它。更现实地说,如果您需要默认值以外的任何值,您可能需要一个域特定的值(通过计算、猜测或分析)。

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

#!/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))
它显示了
imap
imap\u无序
默认使用
chunksize=1
,而
map
max\u chunksize
取决于
nprocess
nitem
(每个进程的块数不固定)和
max\u chunksize
取决于python版本。如果指定了
chunksize
参数,则所有
*map*
函数都会考虑该参数

用法
了解各个岗位的分布情况;指定
--verbose
参数。

这与您的问题无关,但是如果您的iterable是生成器或其他惰性类型,您可能希望使用
imap
而不是
map
,并传递一个显式的
chunksize
参数。哦,这是相关的,如果我不确定
map
的默认值
chunksize
是什么,那么这是适用的-省略指定的默认值支持了我在下面评论中的怀疑-它在开始时将整个批次平均分块到每个进程。正如我在回答中提到的,您可以阅读源代码<代码>映射
采用
chunksize=None
。然后,在
map\u async
(它使用的)中,如果chunksize为None则设置
chunksize,extra=divmod(len(iterable),len(self.pool)*4)
(然后,
如果extra
,则设置
chunksize+=1
)。因此,如果你有8名员工和100份工作,那么
chunksize
将是4.5;还将解释为什么
map
在开始时贯穿整个iterable,它正在查找
len
。我知道如果我要
屈服
,那么无论如何我应该使用
imap
。谢谢大家!正如我在下面所说的,这是一种权衡
map
贯穿整个iterable,这意味着启动前的延迟和/或在内存上运行(对于100整数来说没有什么大不了的,但是对于1000个web spider结果来说,这可能是不可接受的,更不用说,
itertools。重复
…)。但是它更简单一点,您可以得到默认的
chunksize
,而不必计算/测量/猜测一个值。我注意到
imap
的默认chunksize指定为
1
,我想知道
map
的默认值是什么?我的申请是为了什么
$ ./estimate_chunksize.py nprocesses nitems [map_name [chunksize]] [--verbose]