python的multiprocessing和concurrent.futures有啥区别?

Posted

技术标签:

【中文标题】python的multiprocessing和concurrent.futures有啥区别?【英文标题】:What's the difference between python's multiprocessing and concurrent.futures?python的multiprocessing和concurrent.futures有什么区别? 【发布时间】:2014-09-13 19:03:12 【问题描述】:

在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__' 子句来保护代码。这是因为 futures 的实现解决了这个问题还是我们有不同的原因?

更广泛地说,multiprocessingconcurrent.futures 之间有什么区别?什么时候优先于另一个?

编辑: 我最初认为守卫if __name__ == '__main__' 仅对多处理是必需的假设是错误的。显然,Windows 上的两种实现都需要这种保护,而在 unix 系统上则不需要。

【问题讨论】:

呃。我怀疑不需要 if 守卫。根据the documentationProcessPoolExecutor 是建立在multiprocessing 之上的,因此它应该会遇到同样的问题(否则multiprocessing 文档会显示如何避免这种保护,对吧?)。事实上,文档中的示例确实使用了通常的守卫。 你是对的。我很困惑,因为它显然只在 Windows 上是必需的。我必须承认我只在 mac 上测试了期货,因此发现守卫是没有必要的。我会在问题中添加一些注释来强调这一点。 有一次我因为忘记了那个保护而关闭了刀片服务器:) 另见***.com/questions/20776189/… 看起来像 Unix 上的 prefork 模型可以让你摆脱那个应该总是有那个 'if' 行的地方。谁能确认一下? 【参考方案1】:

您实际上也应该使用if __name__ == "__main__" 防护和ProcessPoolExecutor:它使用multiprocessing.Process 来填充它的Pool,就像multiprocessing.Pool 所做的那样,所以关于可腌制性的所有相同警告(尤其是在 Windows 上)等适用。

当被问及 Python 为何同时具有这两种 API 时,this statement made by Jesse Noller(Python 核心贡献者)表示,我相信 ProcessPoolExecutor 最终会取代 multiprocessing.Pool

Brian 和我需要努力实现我们打算(ed)发生的整合 随着人们对 API 感到满意。我的最终目标是删除 除了 MP 中的基本 multiprocessing.Process/Queue 之外的任何东西 并进入并发。*并支持它的线程后端。

目前,ProcessPoolExecutormultiprocessing.Pool 做的事情基本相同,但 API 更简单(也更有限)。如果您可以不使用ProcessPoolExecutor,请使用它,因为我认为从长远来看它更有可能得到增强。请注意,您可以将来自multiprocessing 的所有助手与ProcessPoolExecutor 一起使用,例如LockQueueManager 等,因此需要这些不是使用multiprocessing.Pool 的理由。

不过,它们的 API 和行为存在一些显着差异:

    如果ProcessPoolExecutor 中的进程突然终止,a BrokenProcessPool exception is raised,将中止任何等待池工作的调用,并阻止提交新工作。如果multiprocessing.Pool 发生同样的事情,它会默默地替换终止的进程,但在该进程中完成的工作将永远不会完成,这可能会导致调用代码永远挂起,等待工作完成.

    如果您运行的是 Python 3.6 或更低版本,ProcessPoolExecutor 将缺少对 initializer/initargs 的支持。 Support for this was only added in 3.7)。

    ProcessPoolExecutor 中不支持 maxtasksperchild

    concurrent.futures 在 Python 2.7 中不存在,除非您手动安装 backport。

    如果您在 Python 3.5 以下运行,根据this question,multiprocessing.Pool.map 的性能优于ProcessPoolExecutor.map。请注意,每个工作项的性能差异非常小,因此如果您在非常大的迭代中使用 map,您可能只会注意到较大的性能差异。造成性能差异的原因是multiprocessing.Pool会将传递给map的iterable批量化成chunk,然后将chunk传递给worker进程,这样就减少了父子间IPC的开销。 ProcessPoolExecutor 总是(或默认情况下,从 3.5 开始)一次将一个项目从可迭代对象传递给子对象,由于 IPC 开销增加,这可能导致大型可迭代对象的性能更慢。好消息是这个问题在 Python 3.5 中得到修复,因为 chunksize 关键字参数已添加到 ProcessPoolExecutor.map,当您知道您正在处理大型迭代时,可以使用它来指定更大的块大小。请参阅此bug 了解更多信息。

【讨论】:

从当前的 source 用于 ProcessPoolExecutor.map ,使用 chunksize > 1,看起来元组将被发送到函数,因此函数需要能够处理项目的元组而不是单个项目。你认为我的解释正确吗? @wwii 该函数返回的元组由_process_chunk 方法处理,该方法将元组中的每个条目拉出,并将其传递给用户提供的映射函数。因此,用户在使用 chunksize > 1 时无需更改任何内容。 @Jay Nope,这两个缺陷都已得到解决。 chunksize 在 3.5 中添加到 map 中,initializer/initargs 在 3.7 中添加。【参考方案2】:

if __name__ == '__main__': 仅表示您在命令提示符下使用python <scriptname.py> [options] 而不是python shell 中的import <scriptname> 调用了脚本。

当您从命令提示符调用脚本时,会调用 __main__ 方法。在第二个区块中,

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

无论是从命令提示符调用还是从 shell 导入,都会执行块。

【讨论】:

其实需要保护Windows上multiprocessing脚本的__main__,因为主体在子进程中重新执行。 啊,在那种情况下我误解了这个问题。

以上是关于python的multiprocessing和concurrent.futures有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

Python并发复习3 - 多进程模块 multiprocessing

python多进程multiprocessing

Python的multiprocessing,Queue,Process

python的multiprocessing和concurrent.futures有啥区别?

python ---多进程 Multiprocessing

python多进程——multiprocessing.Process