python的asyncio库以协程为基础,event_loop做为协程的驱动和调度模型。该模型是一个单线程的异步模型,相似于node.js。下图我所理解的该模型node
事件循环经过select()来监听是否存在就绪的事件,若是存在就把事件对应的callback添加到一个task list中。而后从task list头部中取出一个task执行。在单线程中不断的注册事件,执行事件,从而实现了咱们的event_loop模型。python
event_loop中执行的task并非函数mysql
若是咱们把上图当成一个web服务器,左边的一个task当成一次http请求须要执行的完整任务。若是咱们每一次run_task()都执行完一个完整的任务,再去run下一个task。 那这跟普通的串行服务器并无区别。在并发环境下形成的用户体验很是差。web
具体怎么差你能够脑补一下,毕竟咱们如今是使用单线程方式实现的web服务器sql
因此task若是对应一个完整的http请求那么其不多是一个函数,由于函数须要从头执行到尾占用着整个线程。那你以为task是什么呢?编程
若是你不知道答案的话能够看一看个人另外一篇文章 简述python的yield和yield from服务器
没错,task是一个generator,或者能够叫作可中断的函数。task的代码依旧是从上写到下来处理一个http请求。也就是咱们所说的同步的代码组织。网络
可是有所不一样的是,在task中,咱们遇到i/o操做时,咱们就把i/o操做交给selector(稍后咱们解析一下selector,而且把该i/o操做准备完毕后须要执行的回调也告诉selector。而后咱们使用yield保存并中断该函数。并发
此时线程的控制权回到event_loop手中。event_loop首先看一下selector中是否存在就绪的数据,存在的话就把对应的回调放到task list的尾部(如图),而后从头部继续run_task()。异步
你可能想问上面中断的task何时才能继续执行呢?我前一句说过了,event_loop每一次循环都会检测selector中是否存在就绪的i/o操做,若是存在就绪的i/o操做,咱们对应就把callback放到task的尾部,当event_loop执行到这个task时。咱们就能回到咱们刚刚中断的函数继续执行啦,并且此时咱们须要的i/o操做获得的数据也已经准备好了。
这种操做若是你站在函数的角度会有种神奇的感受,在函数眼里,本身须要get遥远服务器的一些数据,因而调动get(),而后瞬间就获得了遥远服务器的数据。没错在函数的眼里就是瞬间获得,这感受就仿佛是穿越到了将来同样。
你可能又想问,为何把callback放到task,而后run一下就回到原有的函数执行位置了?
这我也不知道,我并无深追asyncio的代码,这对于我来讲有些复杂。但若是是个人话,我只要在callback中设置一个变量gen指向咱们的generator就好了,而后只要在callback中gen.send(res_data)
,咱们就能回到中断处继续执行了。若是你有兴趣的话能够本身使用debug来追一下代码。
不过我更推荐你阅读一下这篇博文 深刻理解 Python 异步编程(上)
好比咱们在task中须要执行一个1+2+3+到2000万这样一个操做,这个操做耗时有些长,并且不属于i/o操做,无法交给selector去调度,此时咱们须要本身yield,让其余的task能有机会来使用咱们惟一的线程。这样就又有一个新的问题。yield后,咱们何时再次来执行这个被中断的函数呢?
问题代码示例
import asyncio
def print_sum():
sum = 0
count = 0
for a in range(20000000):
sum += a
count += 1
if count > 1000000:
count = 0
yield
print('1+到2000万的和是{}'.format(sum))
@asyncio.coroutine
def init():
yield from print_sum()
loop = asyncio.get_event_loop()
loop.run_until_complete(init())
loop.run_forever()复制代码
我想咱们能够这样,把这个中断的task直接加入到task list的尾部,而后继续event_loop,这样让其余task有机会执行,而且处理起来更加的简单。 asyncio库也确实是这样作的。
可是asyncio还提供了更好的作法,咱们能够再启动一个线程来执行这种cpu密集型运算
再来看看另一个问题。若是在一个凌晨三点半,你task list此时是空的,那么你的event_loop怎么运做?继续不停的loop等待新的http请求进来? no,咱们不容许如此浪费cpu的资源。asyncio库也不容许。
首先看两行event_loop中的代码片断,也就是上图中右上角部分的select(timeout)部分
event_list = self._selector.select(timeout)
self._process_events(event_list)复制代码
补充一点,做为一台web服务器,咱们老是须要socket()、bind()、listen()、来建立一个监听描述符sockfd,用来监听到来的http请求,与http请求完成三路握手。而后经过accept()操做来获得一个已链接描述符connectfd。
这里的两个文件描述符,此时都存在于咱们的系统中,其中sockfd继续用来执行监听http请求操做。已经链接了的客户端咱们则经过connectfd来与其通讯。通常都是一个sockfd对多个connectfd。
更多的细节推荐阅读 ——《unix网络编程卷一》中的关于socket编程的几章
asyncio对于网络i/o使用了 selector模块,selector模块的底层则是由 epoll()来实现。也就是一个同步的i/o复用系统调用(你定会惊讶于asyncio的居然使用了同步i/o来实现?咱们在下一节来解读一下epoll函数)
这里你能够去读一下python手册中的selector模块,看看这个模块的做用
epoll()函数有个timeout参数,用来控制该函数是否阻塞,阻塞多久。映射到高层就是咱们上面的selector.select(timeout)
中的timeout。原来咱们的event_loop中的存在一个timeout。这样凌晨三点半咱们如何处理event_loop我想你已经内心有数了吧。
asyncio的实现和你想的差很少。若是task list is not None那么咱们的timeout=0也就是非阻塞的。解释一下就是,咱们调用selector.select(timeout = 0 ),该函数会立刻返回结果,咱们对结果作一个上面讲过的处理,也就是self._process_events(event_list)
。而后咱们继续run task。
若是咱们的task list is None, 那么咱们则把timeout=None。也就是设置成阻塞操做。此时咱们的代码或者说线程会阻塞在selector.select(timeout = 0)处,换句话说就是等待该函数的返回。固然这样作的前提是,你往selector中注册了须要等待的socket描述符。
还有一些其余的问题,好比异步mysql是如何在asyncio的基础上实现的,这可能须要去阅读aiomysql库了。
你也许发现,咱们一旦使用了event_loop实现单线程异步服务器,咱们写的全部代码就都不是咱们来控制执行了,代码的执行权所有交给了event_loop,event_loop在适当的时间run task。读过廖雪峰python教程的小伙伴必定看过这句话
这就是异步编程的一个原则:一旦决定使用异步,则系统每一层都必须是异步,“开弓没有回头箭”。
这就是异步编程。
你也许对asyncio的做用,或者使用,或者代码实现有着不少的疑问,我也是如此。可是很抱歉,我并不怎么熟悉python,也没有使用asyncio作过项目,只是出于好奇因此我对python的异步i/o进行了一个了解。
我是一个纸上谈兵的门外汉,到最后我也没能看清asyncio库的具体实现。我接下来的计划中并不打算对asyncio库进行更多的研究,可是我又不甘心这两天对asyncio库的研究付诸东流。因此我留下这篇博文,算是对本身的一个交待!但愿下次可以有机会,可以更加了解python和asyncio的前提下,再写一篇深刻解析python—asyncio的博文。