所有这三种机制都存在的原因是它们具有不同的优势和劣势。
首先,如果您有大量的小型独立任务,并且没有明智的方法将它们批处理(通常,这意味着您正在编写C10k服务器,但这不是唯一可能的情况),那么微线程会胜出。在一切都陷入困境或失败之前,您只能运行几百个操作系统线程或进程。因此,要么使用微线程,要么放弃自动并发并开始编写显式回调或协程。这确实是唯一一次微线程获胜;否则,它们就像操作系统线程一样,除了一些事情不能正常工作。
接下来,如果您的代码受 CPU 限制,则需要进程。微线程本质上是一种单核解决方案;由于 GIL,Python 中的线程通常不能很好地并行化;进程获得操作系统可以处理的尽可能多的并行性。因此,进程将使您的 4 核系统以 4 倍的速度运行您的代码;别的都不会。(事实上,您可能想更进一步并分布在不同的计算机上,但您并没有问过这个问题。)但是如果您的代码是I/O-bound,核心并行没有帮助,所以线程就像像过程一样好。
如果你有很多共享的、可变的数据,事情就会变得艰难。流程需要明确地将所有内容放入可共享的结构中,例如 using multiprocessing.Array
in place of list
,这会变得异常复杂。线程自动共享一切——这意味着到处都有竞争条件。这意味着您需要非常仔细地考虑您的流程并有效地使用锁。通过流程,经验丰富的开发人员可以构建一个适用于所有测试数据的系统,但每次给它一组新的输入时都必须重新组织它。使用线程,有经验的开发人员可以编写运行数周的代码,然后意外地默默地打乱每个人的信用卡号码。
这两个中的哪一个更让你害怕——做那个,因为你更了解这个问题。或者,如果可能的话,退后一步,尝试重新设计您的代码,以使大部分共享数据独立或不可变。这可能是不可能的(不会让事情变得太慢或太难理解),但在决定之前要认真考虑。
如果您有大量独立数据或共享不可变数据,那么线程显然会胜出。进程需要显式共享(multiprocessing.Array
再次类似)或编组。multiprocessing
并且它的第三方替代品使得封送处理对于所有东西都是可腌制的简单情况非常容易,但它仍然不像直接传递值那么简单,而且速度也慢得多。
不幸的是,您需要传递大量不可变数据的大多数情况与需要 CPU 并行性的情况完全相同,这意味着您需要进行权衡。这种权衡的最佳答案可能是您当前的 4 核系统上的操作系统线程,但您在 2 年内拥有的 16 核系统上的进程。(如果您围绕例如multiprocessing.ThreadPool
or组织事物concurrent.futures.ThreadPoolExecutor
,并轻松地切换到Pool
或ProcessPoolExecutor
稍后 - 甚至使用运行时配置切换 - 这几乎可以解决问题。但这并不总是可能的。)
最后,如果您的应用程序本身需要事件循环(例如,GUI 应用程序或网络服务器),请先选择您喜欢的框架。使用PySide
vs.wx
或twisted
vs.gevent
进行编码比使用微线程与 OS 线程进行编码有更大的区别。而且,一旦您选择了框架,看看您可以在多大程度上利用您认为需要真正并发的事件循环。例如,如果您需要一些代码每 30 秒运行一次,请不要为此启动线程(微型或操作系统),请让框架根据需要安排它。