Python之多进程和多线程详解
本指南的目的是解释为什么在Python中需要多线程和多处理,何时使用多线程和多处理,以及如何在程序中使用它们。
文章太长不想看?这里是内容摘要啦~
- 对于IO-bound任务,使用多线程可以提高性能
- 对于IO-bound任务,使用多进程也可以提高性能,但是开销往往比使用多线程要高
- Python GIL意味着在Python程序的任意给定时间内只能执行线程
- 对于CPU bound任务,使用多线程实际会降低性能
- 对于CPU bound任务,使用多进程可以提高性能
- 巫师真的很棒!
很久很久以前,在一个遥远的星系里……
一个聪明而强大的巫师住在一个偏僻的小村庄里。我们叫他邓布利多吧。他不仅聪明、有能力,而且乐于帮助任何请求帮助的人,这意味着人们从四面八方来请求巫师的帮助。我们的故事开始于一个晴朗的日子,一个年轻的旅行者给巫师带来了一卷魔法卷轴。旅行者不知道卷轴里装的是什么,但他知道,如果有人能破译出卷轴的秘密,那一定是伟大的巫师邓布利多。
单线程 单进程
如果你还没有猜出来我这个故事的内涵,其实我是在比喻关于CPU及其功能的。我们的巫师是CPU,而魔法卷轴是一个url列表,它可以引导Python的强大功能和使用该功能的知识。
巫师没费多大力气就破译了卷轴,他的第一个念头就是派他信任的朋友到卷轴上给出的每一个位置去看看并带回他能找到的东西。
如您所见,我们只是使用for循环一个接一个地遍历url并读取响应。多亏了从IPython获得的%%time的魔力,我们能够看到用这个糟糕的方法需要花费12秒。
多线程
巫师的智慧在这片土地上闻名遐迩,他很快就想出了一个更有效的方法。与其将一个人按顺序送到每个地点,不如召集一群(值得信任的)人,同时将他们分别发送到每个地点! 一旦他们都回来了,巫师就可以简单地把他们带回来的一切结合起来。
没错,我们可以使用多线程来同时访问多个url,而不是一个接一个地遍历列表。
这样就好多啦!使用多线程可以显著加快许多与 IO-bound的任务。在这里,读取 URLs 所花费的大部分时间是由于网络延迟。 IO-bound程序大部分时间都在等待输入和输出(您猜对了,类似于巫师需要等待他的朋友/朋友到卷轴中给定的位置并返回)。这可能是来自网络、数据库、文件甚至用户的I/O。这种I/O往往要花费大量的时间,因为源本身可能需要在传递I/O之前执行自己的处理。例如,CPU的工作速度比网络连接传输数据的速度快得多。
注意:多线程在网络数据抓取等任务中非常有用。
多进程
随着时间的流逝,我们巫师的名气越来越大,一个相当讨厌的黑巫师在嫉妒的驱使下,使用狡猾的手段对邓布利多下了一个可怕的咒语。咒语一解开,邓布利多就知道他只有片刻的时间可以打破它。绝望中,他翻遍了自己的咒语书,找到了一个似乎可以奏效的反咒。唯一的问题是,它要求他计算所有质数之和低于100万。
现在,巫师知道,如果有足够的时间,计算值将是微不足道的,但是时间并不是他所拥有的奢侈品。虽然他是一个巫师,但他也受到人性的限制,一次只能计算一个数字。如果是的话,要一个一个地把质数加起来,那就太费时间了。在还剩几秒钟的时候,他突然想起了多年前从魔法卷轴中学到的多重处理咒语。这个咒语可以让他复制自己,把这些数字分开,他就可以同时检查多个数字是否是质数。最后,他所要做的就是把他和他的副本发现的所有质数加起来。
由于现代CPU通常有多个核心,我们可以通过使用多进程模块来加快CPU绑定任务的速度。CPU绑定任务是花费大部分时间在CPU上执行计算的程序(数学计算、图像处理等)。如果计算可以彼此独立地执行,我们就可以将它们分配到可用的CPU内核中,从而显著提高处理速度。
你所要做的就是;
- 定义要应用的函数
- 准备要应用的函数列表
- 使用多进程池生成进程,传递给Pool()的数目将是派生进程的数目。嵌入with语句可以确保进程在执行完成后被终止。
- 使用进程池的map函数组合输出,map函数的输入是要应用于每个项目的函数和项目列表
注意:可以定义该函数,以便执行可以并行执行的任务。例如,函数可能包含将计算结果写入文件的代码
那么,为什么我们需要单独的多进程和多线程呢?如果你尝试使用多线程来提高CPU bound任务,你可能会注意到,实际得到的是性能下降。让我们看看为什么会这样。
就像巫师收到人性的限制,每次只能计算一个数字一样,Python也带有一种称为全局解释器锁(GIL)的东西。Python很乐意让你生成任意数量的线程,但GIL确保在任何给定时间只有一个线程可以执行。
对于IO-bound任务,这非常好。一个线程向URL发出请求,当他等待响应时,他可以将该线程替换为向另一个URL发出另一个请求的另一个线程。因为一个线程在收到响应之前不需要做任何事,所以在给定的时间内只执行一个线程并不重要。
对于CPU bound任务,多线程并没有什么用。因为一次只执行一个线程,即使生成多个线程,并且每个线程都有自己要检查的素数,CPU仍然一次只处理一个线程。实际上,这些数字仍然会被一个接一个的检查。如果在CPU bound 任务中使用多线程,那么处理多线程的开销将导致性能下降。
为了克服这个“限制”,我们使用了多进程模块。多进程不是使用线程,而是使用多个进程。每个进程都有自己的解释器和内存空间,因为GIL不会组织任何事情。本质上,每个进程使用不同的CPU内核同时处理不同的数字。
你可能会注意到,与使用简单的for循环,甚至是多线程相比,使用多进程处理时CPU利用率要高得多。这是因为你的程序使用多个CPU内核,而不仅仅是一个内核。这是个好事。
请记住,multiprocessing
多处理本身就有管理多个进程的开销,这通常比多线程开销更大。(multiprocessing
生成一个单独的解释器,并为每个进程分配一个单独的内存空间)这意味着,根据经验,当可以使用轻量级多线程时,最好使用它(io绑定任务)。当CPU处理成为瓶颈时,通常需要调用多处理模块。但请记住,能力越大,责任越大。
如果一次生成的进程超过CPU的处理能力,你将注意到性能开始下降。这是因为你的进程比内核多,操作系统现在必须做更多的工作来交换CPU内核内外的进程。实际情况可能比简单的解释要复杂的多,但这是基本思想。当我们到达16个进程时,你可以看到我的系统性能的下降。这是因为我的CPU只有16个逻辑内核。
以上就是对Python中多线程和多进程的介绍。感谢您的观看!
让我们勇往直前,征服一切叭!