tornado 异步总结的缘由
最近再看fluent python 中涉及到异步和非阻塞等,最近工作中使用的tornado 也在要使用 异步编程。两者看的云里雾里的。现在做一个梳理,理解其中的相关概念,并且应用到实战中。
tornado 异步
说明: 本程序的环境是Python3.6
先看下原生asyncio 中的几个概念,在tornado 中几乎是一样的。
- event_loop 事件循环:程序开启一个无限循环,工程师会把一些函数注册到事件循环中。当满足事件发生的时候,调用相应的协程函数
- coroutine 协程:协程对象,指一个async关键字定义的函数,它的调用不会立即执行函数,而是返回一个协程对象,协程对象需要注册到事件循环,由事件循环调用。
- task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程的进一步封装,其中包含任务的各种状态。
- future : 代表将来执行或者没有执行的任务的的结果。她和task上没有本质的区别。
- anync/ await 这两个关键自是同时出现的:Python3.5 用于定义协程的关键字,async 定义一个协程,await定义一个协程,用于挂起阻塞的异步调用接口。
–
@gen.coroutine
tornado的 异步特性,必须使用异步的库(这句话如何李理解呢,一会有具体的实例来比较说明)。否则单个进程阻塞,根本不会达到异步效果,Tornado 异步库中最常用的异步库是asynHTTPClient .以及在其基础上实现的OpenID登录验证接口。另外更多的异步库可以在这里找到。包括用的比较多的MongoDB的Driver。
coroutine装饰器可以让本来靠回调的异步编程看起来像同步编程(一会会用代码区别理解)。其中便是利用Python生成器的send() 函数。在生成器中,yield关键字往往会与正常的return相比。它可以被当作迭代器,从而使用next()返回yield的结果。但是生成器还有另外一个用法,就是使用send方法,在生成器内部可以将yield的结果赋值给一个变量,而这个值就是通过外部生成器client来send的。理解上面这句话 首先要弄懂生成器的相关概念,在这里就不拓展生成器的概念了,网上和 flunt python等书都是最好的学习资料。
先看一个异步编程的实例
from tornado import gen
from tornado.platform.asyncio import to_asyncio_future,AsyncIOMainLoop
from tornado.httpclient import AsyncHTTPClient
import asyncio
import time
@gen.coroutine
def gen_test_new():
return "okk"
@gen.coroutine
def aa():
yield gen.sleep(3)
a = "ok"
a = yield gen_test_new()
return a
@gen.coroutine
def tornado_coroutine():
res1, res2 = yield [aa(),aa()]
print(res1)
print(res2)
AsyncIOMainLoop().install()
t1 = time.time()
asyncio.get_event_loop().run_until_complete(to_asyncio_future(tornado_coroutine()))
print(time.time() - t1)
# 打印值
okk
okk
3.002096652984619
上面分析可以看出本来执行两端代码如果是同步的话回事3秒的时间,但是使用了异步并发 只有差不多3秒,一般会多于3秒一点点。
从上面可以看出来每一个函数上面都注解了@gen.coroutine 装饰器,是为了让函数编程异步的,这就解释了上面说的tornado 必须使用异步的库。yield 后面是一个异步的函数,不然会报错,下面来看没有使用注解的运行结果。
from tornado import gen
from tornado.platform.asyncio import to_asyncio_future,AsyncIOMainLoop
from tornado.httpclient import AsyncHTTPClient
import asyncio
import time
# @gen.coroutine
def gen_test_new():
return "okk"
@gen.coroutine
def aa():
yield gen.sleep(