美文网首页大数据 爬虫Python AI SqlPython小哥哥
Python的多线程和多进程——从一个爬虫任务谈起 !

Python的多线程和多进程——从一个爬虫任务谈起 !

作者: 14e61d025165 | 来源:发表于2019-06-28 15:50 被阅读0次

    本文的目的是解释为什么在Python中需要多线程和多处理,何时使用多线程和多进程,以及它们能怎样提高我们程序的性能。

    假设我们的量化模型需要从多个网站爬取一些数据,我们将要对比用单线程和多线程的方法有何性能上的差别。

    1,单线程,单进程

    在单线程、单进程中,我们将用for循环读取一个url列表。

    如您所见,我们只是使用for循环一个接一个地遍历url并读取响应。我们可以使用IPython的%%time函数对消耗的时间进行统计,这个读取13个网页的任务大约需要12秒钟。

    <tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1561708113963" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image

    <input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>

    Python学习交流群:1004391443

    2,多线程

    现在我们改进一下这个程序,我们可以将读取url的任务分配给多个线程来完成,而不是只让一个线程去逐一读取。

    比如4个线程:

    <tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1561708113971" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image

    <input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>

    8个线程:

    <tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1561708113974" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image

    <input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>

    16个线程:

    <tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1561708113978" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image

    <input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>

    用到16个线程时,这个任务的耗时已经从12.3秒缩短到了1.32秒。

    使用多线程可以显著加快许多与io绑定的任务。在这里,读取url所花费的大部分时间是由于网络延迟。与io绑定的程序大部分时间都在等待输入/输出,无所事事。这可能是来自网络、数据库、文件甚至用户的I/O。这种I/O往往要花费大量的时间,因为源本身可能需要在传递I/O之前执行自己的处理。例如,CPU的工作速度比网络连接传输数据的速度快得多。

    多线程可以显著提高我们爬取网页任务的效率。

    3,多重处理

    另一个可以提高效率的手段是多重处理。

    比如我们有一个任务是计算100万以内所有质数的和。

    如果只用单个进程:

    <tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1561708113982" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image

    <input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>

    如果使用多进程:

    <tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1561708113985" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image

    <input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>

    和多线程类似,多进程也是将任务(比如判断一系列数是否是质数)拆分再汇总,以此提高效率。

    由于现代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个逻辑核心。

    <tt-image data-tteditor-tag="tteditorTag" contenteditable="false" class="syl1561708113989" data-render-status="finished" data-syl-blot="image" style="box-sizing: border-box; cursor: text; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial; display: block;"> image

    <input class="pgc-img-caption-ipt" placeholder="图片描述(最多50字)" value="" style="box-sizing: border-box; outline: 0px; color: rgb(102, 102, 102); position: absolute; left: 187.5px; transform: translateX(-50%); padding: 6px 7px; max-width: 100%; width: 375px; text-align: center; cursor: text; font-size: 12px; line-height: 1.5; background-color: rgb(255, 255, 255); background-image: none; border: 0px solid rgb(217, 217, 217); border-radius: 4px; transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) 0s;"></tt-image>

    4,总结

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

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

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

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

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

    相关文章

      网友评论

        本文标题:Python的多线程和多进程——从一个爬虫任务谈起 !

        本文链接:https://www.haomeiwen.com/subject/ijcecctx.html