我阅读了试图获得基本理解的文档,但它仅显示ProcessPoolExecutor
可以绕开Global Interpreter Lock
,我认为这是锁定变量或函数以便并行处理的方法请勿同时更新其值。
我要寻找的是何时使用ProcessPoolExecutor
和何时使用ThreadPoolExecutor
,以及在使用每种方法时应牢记的重点!
答案 0 :(得分:2)
ProcessPoolExecutor
在其自己的子进程中运行每个工人。
ThreadPoolExecutor
在主进程中的不同线程中运行每个工作程序。
全局解释器锁定(GIL)不仅锁定变量或函数;还锁定了全局变量。它锁定整个解释器。这意味着每个内置操作(包括listodicts[3]['spam'] = eggs
之类的操作都是自动线程安全的。
但这也意味着,如果您的代码受CPU限制(也就是说,它花费时间进行计算,而不是等待网络响应),而不是将大部分时间花费在旨在发布的外部库中GIL(如NumPy),一次只能有一个线程拥有GIL。因此,如果您有4个线程,即使您有4个甚至16个内核,大多数情况下,其中3个将坐在那里等待GIL。因此,您的代码不会比之前快4倍,而是会变得更慢。
同样,对于受I / O约束的代码(例如,在一堆服务器上等待响应您发出的一堆HTTP请求),线程就可以了;这仅是受CPU约束的代码。
每个单独的子进程都有其自己的GIL,因此这个问题消失了-即使您的代码受CPU限制,使用4个子进程仍然可以使其运行速度几乎提高了4倍。
但是子进程不共享任何变量。通常,这是一件好事:您将值的(副本)作为函数的参数传入,并返回值的(副本),并且进程隔离保证您可以安全地进行此操作。但是有时(通常出于性能原因,有时还因为您传递的对象无法通过pickle
复制),这是不可接受的,因此您要么需要使用线程,要么使用更复杂的对象multiprocessing
模块中的显式共享数据包装器。
答案 1 :(得分:1)
ProcessPool用于处理CPU绑定的任务,因此您可以受益于多个CPU。
线程用于io绑定任务,因此您可以受益于io等待。