在Python中使用多进程还是多线程?

32
我有一个Python应用程序用于获取一系列数据,并对该集合中的每个数据执行任务。由于存在延迟,因此每个数据都需要逐个执行任务不太理想,我希望它们可以并行处理。在这种情况下,我应该使用多进程还是线程?
我尝试使用线程但遇到了些问题,常常会出现某些任务无法正常启动。

你的“数据集合”有多大?如果非常庞大,你可能不想为每个数据启动线程或进程。 - S.Lott
通常会有1、2或3个数据。 - Ryan
@S.Lott - 您如何将线程/进程数量限制在远小于数据大小的数量? - Adam Greenhall
@Adam Greenhall:那是一个无关的问题;这就是多进程池的用途。我仍在努力理解这个问题。如果有10,000个数据,那么10,000个并发进程(或线程)似乎是一个非常糟糕的想法。如果只有3个,那么问这个问题似乎没有意义,因为最简单的解决方案是最有效的。 - S.Lott
8个回答

30
如果您的计算量真正超出了处理器负载,使用 multiprocessing 模块 可能是最轻量级的解决方案(无论是在内存消耗还是实现难度上)。
如果您的操作受到 I/O 限制,则使用 threading 模块 通常会产生良好结果。确保您使用线程安全的存储方式(例如队列)将数据传递给您的线程。否则,在它们生成时将一个单独的数据块分配给它们使用。 PyPy 专注于性能。它具有多种功能,可助于处理计算量超出处理器负载的任务。虽然软件事务性内存尚未达到生产质量,但 PyPy 也支持其中。承诺是您可以使用比 multiprocessing 更简单的并行或并发机制(后者具有一些棘手的要求)。 Stackless Python 也是一个不错的想法。但如上所述,Stackless 存在移植问题。Unladen Swallow 也曾有所承诺,但现已终止。Pyston 是另一个(未完成的) Python 实现,专注于速度。它采用了与 PyPy 不同的方法,可能会产生更好(或仅仅是不同的)加速效果。

9

任务运行起来像是顺序执行,但你会有一种错觉,认为它们是并行执行的。任务在处理文件或连接I/O时非常有效,并且因为它们是轻量级的。

使用进程池进行多进程可能是正确的解决方案,因为进程并行运行,所以在处理密集计算时非常有效,因为每个进程在一个CPU(或核心)上运行。

设置多进程可能非常容易:

from multiprocessing import Pool

def worker(input_item):
    output = do_some_work()
    return output

pool = Pool() # it make one process for each CPU (or core) of your PC. Use "Pool(4)" to force to use 4 processes, for example.
list_of_results = pool.map(worker, input_list) # Launch all automatically

这是否意味着所有核心都在处理相同的数据?是否可以将 input_list 分割并将每个块传递给不同的核心? - Moj

7
对于小型数据集,可以使用subprocess.Popen创建子进程进行处理。subprocess.Popen可从stdin或命令行参数获取数据块、执行处理,并将结果写入输出文件。当所有子进程完成(或超时)后,只需合并输出文件即可。非常简单。

3
这是一个非常笨重的解决方案。你不仅需要安排将数据提供给外部进程,而且还有巨大的开销。 - Christopher
1
@Christopher。关键在于简单。Unix世界已经使用了这种技术40年。它之所以有效,是因为它很简单。而且,开销并不是真正的“巨大”,因为您正在运行同一二进制映像的多个实例。GNU/Linux已经对此进行了优化。 - S.Lott
8
仅仅因为一个方法已经被长时间使用,并不意味着它是一个好的解决方案。特别是对于计算密集型问题,它并不是一个好的解决方案。这是因为你需要所有进程结构的内存开销以及多个内核转换的延迟,从而产生了巨大的开销。Python的multiprocessing模块并没有像子进程那样真正创建一个新的“进程”。它创建了一个新的解释器上下文,这比创建一个新的操作系统级别的进程要轻得多。 - Christopher
@Christopher:都对。使用subprocess更简单。并不是以某种未定义的方式“更好”。它不太可能更快。有时候实际上会更快,因为在进程启动期间开销更大。关键是多个子进程通常更简单。 - S.Lott

7
你可以考虑使用 Stackless Python解决问题。如果你可以控制那个需要花费很长时间的函数,你可以在其中加入一些stackless.schedule()(让出给下一个协程),或者设置Stackless为抢占式多任务处理
在Stackless中,你没有线程,而是使用轻量级的taskletsgreenlets。它很棒,因为几乎不需要设置就可以实现多任务处理。
然而,Stackless会影响可移植性,因为你必须替换一些标准Python库 -- Stackless去除了对C堆栈的依赖。如果下一个用户也安装了Stackless,那么它非常可移植,但这种情况很少发生。

0

你可能想看看Twisted。它专为异步网络任务而设计。


0

使用CPython的线程模型不会给您带来任何性能提升,因为由于垃圾回收的处理方式,线程实际上并没有并行执行。多进程可以允许并行执行。显然,在这种情况下,您必须有多个可用的核心来分配并行作业。

此相关问题中提供了更多信息。


4
这不是事实。使用线程不会像在C或C++中那样带来如此大的性能提升,但仍会发生一些并发。特别是如果你受限于I/O方面,使用线程会有所帮助。 - Christopher
我之前没有意识到这一点 - 感谢您提供的信息。这里有一个外部参考链接:http://mail.python.org/pipermail/python-dev/2008-May/079461.html。在这个基准测试中,您可以看到您所描述的I/O绑定问题的改进。然而,值得指出的是,与1个Python线程相比,CPU绑定问题实际上运行得更慢了!因此,对于您的应用程序进行分析是必不可少的。 - ire_and_curses

0
如果你可以轻松地对数据进行分区和分离,那么最好在外部进行分区,并将它们馈送到程序的多个进程中,而不是使用线程。

0

IronPython具有真正的多线程功能,不像CPython和它的GIL。因此,根据您正在做什么,它可能值得一看。但是听起来您的用例更适合使用多处理模块。

对于推荐stackless python的人,我不是它的专家,但在我看来,他谈论的是软件“多线程”,实际上根本不是并行的(仍在一个物理线程中运行,因此无法扩展到多个核心)。这只是一种替代的方式来构建异步(但仍然是单线程的,非并行的)应用程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接