异步和非阻塞I/O¶
实时Web功能要求每个用户都有一个长期的、主要是空闲的连接。在传统的同步Web服务器中,这意味着向每个用户投入一个线程,这可能非常昂贵。
为了最小化并发连接的成本,Tornado使用单线程事件循环。这意味着所有应用程序代码都应该以异步和非阻塞为目标,因为一次只能有一个操作处于活动状态。
异步和非阻塞这两个术语是密切相关的,通常可以互换使用,但它们并不完全相同。
舞台调度¶
函数 阻碍 当它在返回前等待某件事情发生时。一个函数可能由于许多原因而阻塞:网络I/O、磁盘I/O、互斥锁等。 每一个 函数块,至少有一点,当它运行和使用CPU时(对于一个极端的例子,它演示了为什么必须像其他类型的块那样重视CPU块,考虑密码散列函数,例如 bcrypt 它设计使用数百毫秒的CPU时间,远远超过典型的网络或磁盘访问)。
函数在某些方面可以是阻塞的,而在其他方面可以是非阻塞的。在 Tornado 的背景下,我们通常在网络I/O的背景下讨论阻塞,尽管所有类型的阻塞都要最小化。
异步的¶
安 异步的 函数在完成前返回,通常会导致在后台执行某些操作,然后在应用程序中触发某些未来操作(与正常操作相反) 同步的 函数,它们在返回之前完成所有要做的事情)。异步接口有多种类型:
回调参数
返回占位符 (
Future
,Promise
,Deferred
)传递到队列
回调注册表(例如posix信号)
不管使用哪种类型的接口,异步函数 根据定义 与调用者的交互方式不同;没有可用的方法使同步函数以对调用者透明的方式异步(如 gevent 使用轻量级线程提供与异步系统相当的性能,但实际上它们并不能使事情异步)。
Tornado中的异步操作通常返回占位符对象 (Futures
,除了一些低级组件,如 IOLoop
使用回调。 Futures
通常通过 await
或 yield
关键词。
实例¶
下面是一个同步函数示例:
from tornado.httpclient import HTTPClient
def synchronous_fetch(url):
http_client = HTTPClient()
response = http_client.fetch(url)
return response.body
这里有一个与本机协同程序异步重写的函数:
from tornado.httpclient import AsyncHTTPClient
async def asynchronous_fetch(url):
http_client = AsyncHTTPClient()
response = await http_client.fetch(url)
return response.body
或者为了与旧版本的python兼容,使用 tornado.gen
模块:
from tornado.httpclient import AsyncHTTPClient
from tornado import gen
@gen.coroutine
def async_fetch_gen(url):
http_client = AsyncHTTPClient()
response = yield http_client.fetch(url)
raise gen.Return(response.body)
协同作战有点神奇,但他们在内部的作用是这样的:
from tornado.concurrent import Future
def async_fetch_manual(url):
http_client = AsyncHTTPClient()
my_future = Future()
fetch_future = http_client.fetch(url)
def on_fetch(f):
my_future.set_result(f.result().body)
fetch_future.add_done_callback(on_fetch)
return my_future
注意协程返回 Future
在提取完成之前。这就是协同训练的原因 异步的 .
使用协程可以做的任何事情,您也可以通过传递回调对象来完成,但是协程提供了一个重要的简化,它允许您以与同步时相同的方式组织代码。这对于错误处理尤其重要,因为 try
/except
块的工作正如您在协程中所期望的那样,而使用回调很难实现这一点。协程将在本指南的下一节中深入讨论。