在Python中有不同的并发方式,下面是一个简单的列表:
process.Popen
,multiprocessing.Process
,旧式os.system
,os.popen
,os.exe*
threading.Thread
greenlet
我知道基于线程的并发和基于进程的并发之间的区别,我知道GIL
在CPython
的线程支持中的影响(但不是太多)。
对于想要实现某种级别的并发性的初学者,如何在它们之间进行选择?或者,它们之间的一般区别是什么?有没有更多的方法可以在Python中进行并发?
我不确定我是否提出了正确的问题,请随时改进这个问题。
答案 0 :(得分:5)
存在所有这三种机制的原因是它们具有不同的优点和缺点。
首先,如果你有大量小的,独立的任务,并且没有合理的方法来批量处理它们(通常,这意味着你正在编写C10k服务器,但这不是唯一可能的情况) ,微线程赢了手。在所有事情陷入困境或失败之前,您只能运行几百个操作系统线程或进程。因此,要么使用微线程,要么放弃自动并发并开始编写显式回调或协同程序。这实际上是唯一的时间微线程获胜;否则,它们就像操作系统线程一样,除了一些东西不能正常工作。
接下来,如果您的代码是CPU-bound,则需要进程。 Microthreads是一种固有的单核解决方案;由于GIL,Python中的线程通常无法很好地并行化;进程获得操作系统可以处理的并行性。因此,流程将让您的4核系统以最快的速度运行您的代码4倍;没有别的。 (事实上,你可能想要更进一步并分布在不同的计算机上,但你没有问过这个问题。)但是如果你的代码是I/O-bound,那么核心并行性就无济于事了,所以线程就像做得好。
如果您拥有大量共享的可变数据,那么事情就会变得艰难。进程需要明确地将所有内容放入可共享的结构中,例如使用multiprocessing.Array
代替list
,这会变得非常复杂。线程自动共享所有内容 - 这意味着各地都有竞争条件。这意味着您需要非常仔细地考虑您的流程并有效地使用锁。通过流程,经验丰富的开发人员可以构建一个适用于所有测试数据的系统,但每次为其提供一组新输入时都必须重新组织。通过线程,经验丰富的开发人员可以编写运行数周的代码,然后无意中默默地扰乱每个人的信用卡号。
这两个中的任何一个都会让你更害怕 - 因为你更了解这个问题。或者,如果可能的话,退一步尝试重新设计代码,使大多数共享数据独立或不可变。这可能不是可能的(不要让事情太慢或太难理解),但在决定之前要考虑它。
如果您有大量独立数据或共享不可变数据,则线程显然会获胜。进程需要显式共享(例如再次multiprocessing.Array
)或编组。 multiprocessing
及其第三方替代品使得编组非常容易,因为一切都是可选择的简单情况,但它仍然不像直接传递值那么简单,而且速度也慢得多。
不幸的是,大多数需要传递大量不可变数据的情况与需要CPU并行性的情况完全相同,这意味着您需要权衡。这种权衡的最佳答案可能是您当前的4核系统上的操作系统线程,但是您在2年内拥有的16核系统上的流程。 (如果您组织了一些事情,例如multiprocessing.ThreadPool
或concurrent.futures.ThreadPoolExecutor
,并且稍后切换到Pool
或ProcessPoolExecutor
,或者甚至使用运行时配置开关 - 这几乎可以解决问题。但这并不总是可行的。)
最后,如果您的应用程序本身需要事件循环(例如,GUI应用程序或网络服务器),请首先选择您喜欢的框架。使用PySide
与wx
或twisted
与gevent
进行编码比使用微线程与操作系统线程进行编码有更大的区别。而且,一旦你选择了框架,看看你可以利用它认为你需要真正的并发性的事件循环。例如,如果您需要每30秒运行一些代码,请不要为此启动一个线程(微操作系统或操作系统),请求框架根据需要安排它。