异步IO
异步IO是指当程序需要执行一个耗时的IO操作时,它只发出IO操作指令,并不等待IO结果,然后就去执行其他代码了,当IO返回结果时,再通知CPU进行处理。
异步IO模型需要一个消息循环,在消息循环中,主线程不断地重复”读取消息-处理消息”这一过程:
1  | loop = get_event_loop()  | 
如果是使用同步IO模型,在”发出IO请求”到收到”IO完成”的这段时间里,主线程只能挂起,但在异步IO模型下,主线程并没有挂起,而是在消息循环中继续处理其他消息。这样,在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。
协程
协程,又称微线程,纤程。英文名Coroutine。
协程看上去就像子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:
1  | def A():  | 
假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,但是在A中是没有调用B的,结果可能是:
1
2
x
y
3
z
在Python中协程是通过generator来实现的。
下面的代码,使用协程实现生产者-消费者模型:
1  | 
  | 
输出结果:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consuming return: 200 OK...
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consuming return: 200 OK...
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consuming return: 200 OK...
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consuming return: 200 OK...
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consuming return: 200 OK...
consumer函数是一个generator,把一个consumer传入producer后:
producer首先调用c.send(None)启动生成器一旦生成了东西,通过
c.send(n)切换到consumer执行consumer通过yield拿到消息,处理,又通过yield把结果传回producer拿到consumer处理的结果,继续生产下一条消息producer决定不生产了,通过c.close()关闭consumer,整个过程结束。
asyncio
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio的编程模型就是一个消息循环。从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。
如下代码:
1  | 
  | 
@asyncio.coroutine把hello生成器标记为coroutine类型,用tasks封装两个hello协程,把这两个协程扔到EventLoop中执行。asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待。程序最终输出以下结果:
Hello world! (<_MainThread(MainThread, started 5476)>)
Hello world! (<_MainThread(MainThread, started 5476)>)
(暂停约1秒)
Hello again! (<_MainThread(MainThread, started 5476)>)
Hello again! (<_MainThread(MainThread, started 5476)>)
由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。
如果把asyncio.sleep(1)换成真正的IO操作,则多个coroutine就可以由一个线程并发执行。
async/await
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。
请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
- 把
@asyncio.coroutine替换为async - 把
yield from替换为await 
使用async/await替换上面hello协程:
1  | async def hello():  | 
aiohttp
asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。
安装aiohttp:
pip install aiohttp
编写一个HTTP服务器,分别处理以下URL:
/- 首页返回b'<h1>Index</h1>'/hello/{name}- 根据URL参数返回文本hello, %s!
1  | import asyncio  | 
index协程,当访问首页/时被调用,返回一个<h1>Index</h1>的html文档。hello协程,当访问/hello/{name}时被调用,从request.match_info['name']取出对应的参数,然后再返回对应的html文档。init协程,添加访问的url,创建一个http服务。
运行程序,在浏览器中输入http://127.0.0.1:8000/,可以看到如下结果:

在浏览器中输入http://127.0.0.1:8000/hello/web,可以看到如下结果:**

aiohttp已经是一个Web框架了,但是对于使用者来说,还是相对比较底层,编写一个URL的处理函数需要这么几步:
编写一个协程的处理函数,如上面的
index、hello。从
request中获取传入的参数,如上面的hello函数的request.match_info['name']。返回一个
Response对象。
对于这些重复的工作可以由框架完成。
Web框架
get和post装饰器
在coroweb.py模块中,定义get、post装饰器,把一个函数映射为一个URL处理函数:
1  | 
  | 
@get、或@post装饰器添加了__method__属性,表示http的GET或POST请求,__route__属性,表示http请求的URI资源地址,一个函数通过@get、或@post的装饰就附带了URL信息,例如上面的index、hello可以写成:
1  | 
  | 
add_routes
例如在handlers.py模块定义了使用@get或@post装饰器的URL处理函数,通过调用add_routes函数进行注册。add_routes函数的定义如下:
1  | def add_route(app, fn):  | 
在handlers.py模块中添加如下函数,这样当调用add_routes(app, 'handlers')就会自动注册handlers.py模块含有@get或@post装饰器的URL处理函数:
1  | from coroweb import get  | 
在apps.py模块中定义如下的测试代码,运行程序,在浏览器中输入http://127.0.0.1:8000/、http://127.0.0.1:8000/hello/web可以看到和上面一样的效果。
1  | async def init(loop):  | 
RequestHandler
通过上面的@get和@post装饰器,我们就可以把一个URL请求和对应的处理函数关联起来了。但是我们在处理函数中还是要从request获取参数,进行分析,对于这个步骤,可以使用下面的RequestHandler类来实现。
RequestHandler 定义如下:
1  | def get_required_kw_args(fn):  | 
上面的RequestHandler实现了__call__函数,所以可以想使用函数那样调用,无需创建实例后调用。
RequestHandler从URL函数中分析其需要接收的参数,从request中获取必要的参数,调用URL函数,有了RequestHandler定义,我们就可以在handlers.py的模块中使用各种参数的URL,例如:
1  | 
  | 
将上面的add_route函数中下面的语句:
app.router.add_route(method, path, fn)
修改为:
app.router.add_route(method, path, RequestHandler(app, fn))
把handlers.py定义的index、hello函数修改为下面的定义:
1  | 
  | 
运行apps.py模块中的测试程序,在浏览器中输入http://127.0.0.1:8000/、http://127.0.0.1:8000/hello/web可以看到和上面一样的效果。
middleware
middleware是一种拦截器,一个URL在被某个函数处理前,可以改变URL的输入、输出。middleware的用处就在于把通用的功能从每个URL处理函数中拿出来,集中放到一个地方。例如,一个记录URL日志的logger可以简单定义如下:
在app.py中定义如下的logger_factory函数:
1  | async def logger_factory(app, handler):  | 
对于上面的index、hello的URL处理函数返回的web.Response对象,我们可以定义一个response_factory拦截器进行处理:
在app.py中定义如下的response_factory函数:
1  | 
  | 
response_factory函数,在调用URL处理函数后,对其返回值的类型,进行判断,并返回相应的web.Response的对象,如返回的类型是str,对应的是html文档,返回的类型是dict,对应的是json对象。
有了上面的response_factory拦截器,handlers.py的index、hello的URL处理函数,可以修改为如下:
1  | 
  | 
在app.py的init函数中添加logger_factory、response_factory拦截器,代码如下:
1  | async def init(loop):  | 
运行apps.py模块中的测试程序,在浏览器中输入http://127.0.0.1:8000/、http://127.0.0.1:8000/hello/web可以看到和上面一样的效果。
使用Web框架和ORM框架
有了上面的Web框架,对于使用者来说,就非常方便了。如果要处理一个URL,只需要在handlers.py模块中添加相应的处理函数即可。
现在我们可以上文中的ORM框架结合起来使用,例如,下面的get_users函数用于访问数据库中user表的数据。
在handlers.py模块中定义get_users函数:
1  | 
  | 
在app.py的init函数中,创建数据库的连接池:
1  | async def init(loop):  | 
运行app.py测试程序,在浏览器中输入http://127.0.0.1:8000/users,可以看到数据库中user表的数据,以json形式返回:
