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 的实现解决了这个问题还是我们有不同的原因?
更广泛地说,multiprocessing
和 concurrent.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 之外的任何东西 并进入并发。*并支持它的线程后端。
目前,ProcessPoolExecutor
与multiprocessing.Pool
做的事情基本相同,但 API 更简单(也更有限)。如果您可以不使用ProcessPoolExecutor
,请使用它,因为我认为从长远来看它更有可能得到增强。请注意,您可以将来自multiprocessing
的所有助手与ProcessPoolExecutor
一起使用,例如Lock
、Queue
、Manager
等,因此需要这些不是使用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,Queue,Process