使用带有关键字参数的multiprocessing.Pool.map()函数?

时间:2016-03-03 01:15:30

标签: python multiprocessing

我正在尝试将关键字参数传递给Python map实例中的multiprocessing.Pool函数。

Using map() function with keyword arguments推断,我知道我可以使用functools.partial(),如下所示:

from multiprocessing import Pool
from functools import partial
import sys

# Function to multiprocess
def func(a, b, c, d):
    print(a * (b + 2 * c - d))
    sys.stdout.flush()

if __name__ == '__main__':
    p = Pool(2)
    # Now, I try to call func(a, b, c, d) for 10 different a values,
    # but the same b, c, d values passed in as keyword arguments
    a_iter = range(10)
    kwargs = {'b': 1, 'c': 2, 'd': 3}

    mapfunc = partial(func, **kwargs)
    p.map(mapfunc, a_iter)

输出正确:

0
2
4
6
8
10
12
14
16
18

这是最好的做法(大多数" pythonic"方式)吗?我觉得:

1)Pool是常用的;

2)常用关键字参数;

3)但是像我上面的例子那样的组合用法有点像" hacky"实现这一目标的方法。

1 个答案:

答案 0 :(得分:2)

如果默认参数很大,则使用partial可能不是最理想的。传递给map的函数在发送给worker时重复pickle - 对于iterable中的每个参数都是一次;一个全局Python函数(基本上)pickle - 通过发送限定名称(因为在另一侧定义相同的函数而不需要传输任何数据),而partialpickle -ed作为函数的pickle和所有提供的参数。

如果kwargs都是小原语,就像你的例子一样,这并不重要;发送额外参数的增量成本是微不足道的。但是,如果kwargs很大,比如说kwargs = {'b': [1] * 10000, 'c': [2] * 20000, 'd': [3]*30000},那么这是一个令人讨厌的代价。

在这种情况下,您有一些选择:

  1. partial之类的全局级别滚动您自己的功能,但pickle的方式不同:

    class func_a_only(a):
        return func(a, 1, 2, 3)
    
  2. 使用initializer参数Pool,以便每个工作进程设置一次状态,而不是每个任务一次,这样即使您正在工作,也可以确保数据可用在基于spawn的环境中(例如Windows)

  3. 使用Manager在所有流程中共享单个数据副本

  4. 可能还有其他一些方法。点是,partial适用于不会产生巨大pickle s的参数,但如果绑定参数很大,它可以杀死你。

    注意:在这种特殊情况下,如果您使用的是Python 3.3+,那么您实际上需要 partial并避免使用dict支持tuple s可以节省大量的开销。如果不添加任何新功能,只需要一些导入,就可以替换:

    kwargs = {'b': 1, 'c': 2, 'd': 3}
    mapfunc = partial(func, **kwargs)
    p.map(mapfunc, a_iter)
    

    使用:

    from itertools import repeat
    
    p.starmap(func, zip(a_iter, repeat(1), repeat(2), repeat(3)))
    

    达到类似的效果。要明确的是,partial这个"修复"没有错。 (这两种方法在酸洗大型物体时会遇到同样的问题),这只是一种偶尔有用的替代方法。