本篇文章主要是讲解asyncio模块的实现原理. 这个系列还有另外两篇文章:
一. asyncio模块简介
asyncio是python3.4开始内置的一个标准库, 可以用于编写异步的并发代码, 因此非常适合用在IO密集型操作.
现在运行如下代码:
importasyncioimporttime
asyncdeftask(i):print('task{} start at {}'.format(i, time.ctime()))#asyncio.sleep的效果与time.sleep类似, 让程序睡眠n秒
await asyncio.sleep(3)print('task{} end at {}'.format(i, time.ctime()))
tasks= asyncio.wait([task(i) for i in range(3)])
asyncio.run(tasks)
运行结果如下:
三个任务实际是处于同一线程的, 但它们的执行顺序不是start->end->start->end这种串行模式, 而是几乎同时开始, 同时结束, asyncio模块的作用就是, 使用异步的方式实现单线程并发的效果. 最简单的使用步骤如下:
首先, 在定义函数的时候使用关键字async, 这个函数就不是个普通函数了, 调用的时候不会执行内部代码, 而是返回一个coroutine对象, 即协程, 这一点与生成器函数类似.
然后, 在协程函数中的耗时操作前面加上await关键字, 注意await后面必须是可等待对象, 比如asyncio.sleep(n), 可等待对象在本文的第二节有详细的讲解.
最后, 调用asyncio.wait将协程列表打包, 打包结果给asyncio.run运行即可.
二. asyncio实现原理
要理解asyncio的原理, 需要理解如下几个概念: 协程, 事件循环, future/task. 其中协程就是用户自己定义的任务, 事件循环负责监听事件和回调, future/task则主要负责管理回调, 以及驱动协程.
1. 事件循环
事件循环负责同时对多个事件进行监听, 当监听到事件时, 就调用对应的回调函数, 进而驱动不同的任务. 上一节代码最后的asyncio.run, 其本质就是创建一个事件循环, 然后一直运行事件循环, 直到所有任务结束为止.
首先看看上篇文章最后的爬虫代码:
importselectimportsocketimporttime
req= 'GET / HTTP/1.0\r\nHost:cn.bing.com\r\n\r\n'.encode('utf8')
address= ('cn.bing.com', 80)
db=[]classGenCrawler:'''这里使用一个类将生成器封装起来,如果要驱动生成器,就调用next_step方法
另外,这个类还可以获取到使用的socket对象'''
def __init__(self):
self.sock=socket.socket()
self.sock.setblocking(0)
self._gen=self._crawler()defnext_step(self):
next(self._gen)def_crawler(self):
self.sock.connect_ex(address)yieldself.sock.send(req)
response= b''
while 1:yieldchunk= self.sock.recv(1024)if chunk == b'':
self.sock.close()break
else:
response+=chunk
db.append(response)defevent_loop(crawlers):#首先,建立sock与crawler对象的映射关系,便于由socket对象找到对应的crawler对象
#建立映射的同时顺便调用crawler的next_step方法,让内部的生成器运行起来
sock_to_crawler ={}for crawler incrawlers:
sock_to_crawler[crawler.sock]=crawler
crawler.next_step()#select.select需要传入三个列表,分别对应要监听的可读,可写和错误事件的socket对象集合
readable =[]
writeable= [crawler.sock for crawler incrawlers]
errors=[]while 1:
rs, ws, es=select.select(readable, writeable, errors)for sock inws:#当socket对象连接到服务器时,会创建可读缓冲区和可写缓冲区
#由于可写缓冲区创建时为空,因此连接成功时,就触发可写事