python的multiprocessing和concurrent.futures有什么区别?

24 投票
2 回答
10475 浏览
提问于 2025-04-18 14:20

在Python中实现多进程的一种简单方法是

from multiprocessing import Pool

def calculate(number):
    return number

if __name__ == '__main__':
    pool = Pool()
    result = pool.map(calculate, range(4))

另一种基于“未来”的实现方式是

from concurrent.futures import ProcessPoolExecutor

def calculate(number):
    return number

with ProcessPoolExecutor() as executor:
    result = executor.map(calculate, range(4))

这两种方法基本上做的是一样的事情,但有一个明显的不同点就是,我们不需要像往常一样用if __name__ == '__main__'来保护代码。这是因为“未来”的实现已经处理好了这个问题,还是有其他原因呢?

更广泛地说,multiprocessingconcurrent.futures之间有什么区别?在什么情况下更倾向于使用其中一个呢?

编辑: 我最开始认为if __name__ == '__main__'这个保护只对多进程必要,这个想法是错的。显然,在Windows系统上,这个保护在两种实现中都是需要的,而在Unix系统上则不需要。

2 个回答

3

if __name__ == '__main__': 这句话的意思是,你是在命令行里用 python <scriptname.py> [options] 来运行这个脚本,而不是在 Python 交互式环境中用 import <scriptname> 来引入它。

当你在命令行运行一个脚本时,__main__ 这个方法会被调用。在第二个代码块中,

with ProcessPoolExecutor() as executor:
    result = executor.map(calculate, range(4))

这个代码块会被执行,无论你是从命令行运行的,还是从交互式环境中引入的。

29

其实你在使用 ProcessPoolExecutor 的时候,也应该加上 if __name__ == "__main__" 这个保护措施。因为它在后台使用了 multiprocessing.Process 来填充它的 Pool,就像 multiprocessing.Pool 一样,所以关于可序列化(特别是在Windows上)等问题都是一样的。

我认为 ProcessPoolExecutor 最终是为了取代 multiprocessing.Pool,这是根据 Jesse Noller(一个Python核心贡献者) 的说法,当被问到为什么Python有这两个API时,他提到:

Brian和我需要进行整合,我们希望随着人们对这些API的熟悉,能够实现这一点。我的最终目标是把除了基本的 multiprocessing.Process/Queue 之外的东西都移出MP,放到 concurrent.* 中,并支持线程后端。

目前,ProcessPoolExecutormultiprocessing.Pool 基本上做的是一样的事情,只是API更简单(而且功能有限)。如果你能使用 ProcessPoolExecutor,那就用它,因为我觉得它在长期内更可能会得到改进。值得注意的是,你可以在 ProcessPoolExecutor 中使用所有来自 multiprocessing 的辅助工具,比如 LockQueueManager 等等,所以需要这些工具并不是使用 multiprocessing.Pool 的理由。

不过,它们的API和行为有一些显著的不同:

  1. 如果 ProcessPoolExecutor 中的一个进程突然终止,会抛出 一个 BrokenProcessPool 异常,这会中止所有等待池执行工作的调用,并阻止新的工作被提交。如果 multiprocessing.Pool 中发生同样的事情,它会默默地替换掉终止的进程,但在那个进程中正在进行的工作将永远无法完成,这可能会导致调用代码一直挂起,等待工作完成。

  2. 如果你使用的是Python 3.6或更低版本,ProcessPoolExecutor 不支持 initializer/initargs这个功能是在3.7版本中才添加的

  3. ProcessPoolExecutor 不支持 maxtasksperchild

  4. concurrent.futures 在Python 2.7中不存在,除非你手动安装了后向移植版本。

  5. 如果你使用的是低于Python 3.5的版本,根据 这个问题multiprocessing.Pool.map 的性能优于 ProcessPoolExecutor.map。需要注意的是,性能差异在每个工作项上是非常小的,所以你可能只有在对非常大的可迭代对象使用 map 时才会注意到明显的性能差异。性能差异的原因是 multiprocessing.Pool 会将传递给 map 的可迭代对象分批处理,然后将这些批次传递给工作进程,这样可以减少父进程和子进程之间的通信开销。而 ProcessPoolExecutor 总是(或者从3.5开始默认)一次只将可迭代对象中的一个项目传递给子进程,这在处理大可迭代对象时可能会导致性能显著下降,因为增加了进程间通信的开销。好消息是这个问题在Python 3.5中得到了修复,因为 ProcessPoolExecutor.map 添加了 chunksize 这个关键字参数,可以在处理大可迭代对象时指定更大的批量大小。有关更多信息,请参见这个 bug

撰写回答