Python多线程/多进程释疑:为啥、何时、怎么用?

2019年06月27日 由 sunlei 发表 97807 0
本指南的目的是解释为什么在Python中需要多线程和多处理,何时使用多线程和多处理,以及如何在程序中使用它们。作为一名人工智能研究人员,我在为我的模型准备数据时广泛使用它们!



在进入正题之前,我先讲一个故事:

很久很久以前,在一个遥远的星系里……

一个聪明而强大的巫师住在一个偏僻的小村庄里。我们叫他邓布利多吧。他不仅聪明、有能力,而且乐于帮助任何请求帮助的人,这意味着人们从四面八方来请求巫师的帮助。我们的故事开始于一个晴朗的日子,一个年轻的旅行者给巫师带来了一卷魔法卷轴。旅行者不知道卷轴里装的是什么,但他知道,如果有人能破译出卷轴的秘密,那一定是伟大的巫师邓布利多。

第一章:单线程,单进程


如果你还没有猜出来我这个故事的内涵,其实我是在比喻关于CPU及其功能的。我们的巫师是CPU,而魔法卷轴是一个url列表,它可以引导Python的强大功能和使用该功能的知识。

巫师没费多大力气就破译了卷轴,他的第一个念头就是派他信任的朋友到卷轴上给出的每一个位置去看看并带回他能找到的东西。




如您所见,我们只是使用for循环一个接一个地遍历url并读取响应。多亏了从IPython获得的%%时间的魔力,我们可以看到我可怜的互联网大约需要12秒钟。

第二章:多线程


巫师的智慧在这片土地上闻名遐迩,他很快就想出了一个更有效的方法。与其将一个人按顺序送到每个地点,不如召集一群(值得信任的)人,同时将他们分别发送到每个地点! 一旦他们都回来了,巫师就可以简单地把他们带回来的一切结合起来。

没错,我们可以使用多线程来同时访问多个url,而不是一个接一个地遍历列表。





好多了!就像…魔法。使用多线程可以显著加快许多与io绑定的任务。在这里,读取url所花费的大部分时间是由于网络延迟。与io绑定的程序大部分时间都在等待输入/输出(您猜对了,类似于巫师需要等待他的朋友/朋友到滚动条中给定的位置并返回)。这可能是来自网络、数据库、文件甚至用户的I/O。这种I/O往往要花费大量的时间,因为源本身可能需要在传递I/O之前执行自己的处理。例如,CPU的工作速度比网络连接传输数据的速度快得多。

注意:多线程在web抓取等任务中非常有用。

第三章:多处理


随着时间的流逝,我们巫师的名气越来越大,一个相当讨厌的黑巫师在嫉妒的驱使下,使用狡猾的手段对邓布利多下了一个可怕的咒语。咒语一解开,邓布利多就知道他只有片刻的时间可以打破它。绝望中,他翻遍了自己的咒语书,找到了一个似乎可以奏效的反咒。唯一的问题是,它要求他计算所有质数之和低于100万。

现在,巫师知道,如果有足够的时间,计算值将是微不足道的,但是时间并不是他所拥有的奢侈品。虽然他是一个巫师,但他也受到人性的限制,一次只能计算一个数字。如果是的话,要一个一个地把质数加起来,那就太费时间了。在还剩几秒钟的时候,他突然想起了多年前从魔法卷轴中学到的多重处理咒语。这个咒语可以让他复制自己,把这些数字分开,他就可以同时检查多个数字是否是质数。最后,他所要做的就是把他和他的副本发现的所有质数加起来。







由于现代CPU通常有多个核心,我们可以通过使用多处理模块来加快CPU绑定任务的速度。CPU绑定任务是花费大部分时间在CPU上执行计算的程序(数学计算、图像处理等)。如果计算可以彼此独立地执行,我们就可以将它们分配到可用的CPU内核中,从而显著提高处理速度。

你所要做的就是;

  1. 定义要应用的函数

  2. 准备要应用功能的项目列表

  3. 使用Pool生成进程。传递给Pool()的数字将是生成的进程数。在with语句中嵌入可以确保在完成执行后终止进程。

  4. 使用池进程的map函数组合输出。映射函数的输入是要应用于每个项的函数,以及项列表。


注意:可以定义该函数,以便执行任何可以并行执行的任务。例如,函数可能包含将计算结果写入文件的代码。

那么,为什么我们需要单独的多处理和多线程呢?如果您尝试使用多线程来提高CPU绑定任务的性能,您可能会注意到,实际上得到的是性能下降。异端!让我们看看为什么会这样。

就像巫师受到人性的限制,每次只能计算一个数字一样,Python也带有全局解释器锁(GIL)。Python会很乐意让您生成任意数量的线程,但是GIL确保在任何给定的时间只有一个线程执行。

对于一个io绑定的任务,这完全没问题。一个线程向一个URL发出请求,当它等待响应时,可以将该线程替换为向另一个URL发出另一个请求的另一个线程。因为一个线程在收到响应之前不需要做任何事情,所以在给定的时间内只执行一个线程并不重要。

对于CPU绑定的任务,因为一次只执行一个线程,即使生成多个线程,并且每个线程都有自己的数目来检查素数,CPU仍然一次只处理一个线程。实际上,这些数字仍然会被一个接一个地检查。如果在CPU绑定的任务中使用多线程,那么处理多线程的开销将导致性能下降。

为了克服这个“限制”,我们使用了多处理模块。多处理不是使用线程,而是使用多个进程。每个进程都有自己的解释器和内存空间,因此GIL不会阻止任何事情。本质上,每个进程使用不同的CPU内核同时处理不同的数字。

您可能会注意到,与使用简单的for循环,甚至多线程相比,使用多处理时CPU利用率要高得多。这是因为您的程序使用多个CPU内核,而不仅仅是一个内核。

请记住,多处理本身就有管理多个进程的开销,这通常比多线程开销更大。(多处理生成一个单独的解释器,并为每个进程分配一个单独的内存空间)这意味着,根据经验,当可以使用轻量级多线程时,最好使用它(io绑定任务)。当CPU处理成为瓶颈时,通常需要调用多处理模块。但请记住,能力越大,责任越大。

如果一次生成的进程超过CPU的处理能力,您将注意到性能开始下降。这是因为操作系统现在必须做更多的工作来交换CPU内核内外的进程,因为您的进程比内核多。实际情况可能比简单的解释要复杂得多,但这是基本思想。当我们达到16个进程时,您可以看到我的系统性能下降。这是因为我的CPU只有16个逻辑核心。

第四章:TLDR(总之);



  • 对于io绑定的任务,使用多线程可以提高性能。

  • 对于io绑定的任务,使用多处理也可以提高性能,但是开销往往比使用多线程高。

  • Python GIL意味着在Python程序的任何给定时间内只能执行线程。

  • 对于CPU绑定的任务,使用多线程实际上会降低性能。

  • 对于CPU绑定的任务,使用多处理可以提高性能。


以上就是对Python中多线程和多处理的介绍。现在请你,勇往直前,征服一切!
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消