1.前言在执行一些IO密集型任务的时候,程序常常会因为等待IO而阻塞。比如在网络爬虫中,如果我们使用requests库来进行请求的话,如果网站响应速度过慢,程序一直在等待网站响应,最后导致其爬取效率是非常非常低的。为了解决这类问题,本文就来探讨一下Python中异步协程来加速的方法,此种方法对于IO密集型任务非常有效。如将其应用到网络爬虫中,爬取效率甚至可以成百倍地提升。
2.基本了解在了解异步协程之前,我们首先得了解一些基础概念,如阻塞和非阻塞、同步和异步、多进程和协程。2.1阻塞阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的。常见的阻塞形式有:网络I/O阻塞、磁盘I/O阻塞、用户输入阻塞等。
首先我们引入了asyncio这个包,这样我们才可以使用async和await,然后我们使用async定义了一个execute()方法,方法接收一个数字参数,方法执行之后会打印这个数字。随后我们直接调用了这个方法,然而这个方法并没有执行,而是返回了一个coroutine协程对象。
这里我们定义了loop对象之后,接着调用了它的create_task()方法将coroutine对象转化为了task对象,随后我们打印输出一下,发现它是pending状态。
发现其效果都是一样的。3.2绑定回调另外我们也可以为某个task绑定一个回调方法,来看下面的例子:
在这里我们定义了一个request()方法,请求了百度,返回状态码,但是这个方法里面我们没有任何print()语句。随后我们定义了一个callback()方法,这个方法接收一个参数,是task对象,然后调用print()方法打印了task对象的结果。
3.4协程实现前面说了这么一通,又是async,又是coroutine,又是task,又是callback,但似乎并没有看出协程的优势啊?反而写法上更加奇怪和麻烦了,别急,上面的案例只是为后面的使用作铺垫,接下来我们正式来看下协程在解决IO密集型任务上有怎样的优势吧!上面的代码中,我们用一个网络请求作为示例,这就是一个耗时等待的操作,因为我们请求网页之后需要等待页面响应并返回结果。
这里我们定义了一个Flask服务,主入口是index()方法,方法里面先调用了sleep()方法休眠3秒,然后接着再返回结果,也就是说,每次请求这个接口至少要耗时3秒,这样我们就模拟了一个慢速的服务接口。注意这里服务启动的时候,run()方法加了一个参数threaded,这表明Flask启动了多线程模式,不然默认是只有一个线程的。
可以发现和正常的请求并没有什么两样,依然还是顺次执行的,耗时15秒,平均一个请求耗时3秒,说好的异步处理呢?其实,要实现异步处理,我们得先要有挂起的操作,当一个任务需要等待IO结果的时候,可以挂起当前任务,转而去执行其他任务,这样我们才能充分利用好资源,上面方法都是一本正经的串行走下来,连个挂起都没有,怎么可能实现异步?想太多了。
这次它遇到await方法确实挂起了,也等待了,但是最后却报了这么个错,这个错误的意思是requests返回的Response对象不能和await一起使用,为什么呢?
领取专属 10元无门槛券
私享最新 技术干货