Python
中,多线程最常见的一个场景就是爬虫,例如这样一个需求,有多个结构一样的页面需要爬取,例如下方的URL(豆瓣阿凡达影评,以10个为例)url_list = [ 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=0', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=20', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=40', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=60', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=80', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=100', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=120', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=140', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=160', 'https://movie.douban.com/subject/1652587/reviews?sort=time&start=180']
如果依次爬取,请求第一个页面——得到返回数据——解析数据——提取、存储数据——请求第二个页面,按照这样的思路,那么大量时间都会浪费在请求、返回数据上,如果在等待第一个页面返回数据时去请求第二个页面,就能有效的提高效率,多线程就可以实现这样的功能。
在Python中实现多线程的方法也很多,我将基于 threading
模块一点一点介绍,注意:本文主要目的是希望用最少的话教会大家如何在代码上中实现多线程,因此侧重的是应用层面,不会过多深入多线程背后的实现。在最后,我们会回到如何使用threading
模块来解决上面的爬虫问题。
让我们先从一个简单的例子开始,定义do_something
函数,执行该函数需要消耗1秒
import time start = time.perf_counter() def do_something(): print("-> 线程启动") time.sleep(1) print("-> 线程结束") do_something() finish = time.perf_counter() print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
上面的代码不难理解,执行do_something
并计算耗时,结果很明显应该是1s
-> 线程启动 -> 线程结束 全部任务执行完成,耗时 1.01 秒
现在如果需要执行两次do_something
,按照最基本的思路
import time start = time.perf_counter() def do_something(): print("-> 线程启动") time.sleep(1) print("-> 线程结束") do_something() do_something() finish = time.perf_counter() print(f"全部任务执行完成,耗时 {round(finish - start,2)} 秒")
执行上面代码结果也很容易猜到是2秒
-> 线程启动 -> 线程结束 -> 线程启动 -> 线程结束 全部任务执行完成,耗时 2.01 秒
这就是最常规的 同步 思路,在CPU执行第一个函数,也就是等待1s的时间内,什么也不干,等第一个函数执行完毕后再执行第二个函数
很明显,这样让CPU干等着啥也不干并不是一个很好的选择,而多线程就是解决这一问题的方法之一,让CPU在等待某个任务完成时去执行更多的操作,将整个过程简化为下图流程,这样就能充分节省时间
现在使用threading
来通过多线程的方式实现上面的过程,非常简单,定义两个线程并依次启动即可