异步和非阻塞I/O

实时Web功能要求每个用户都有一个长期的、主要是空闲的连接。在传统的同步Web服务器中,这意味着向每个用户投入一个线程,这可能非常昂贵。

为了最小化并发连接的成本,Tornado使用单线程事件循环。这意味着所有应用程序代码都应该以异步和非阻塞为目标,因为一次只能有一个操作处于活动状态。

异步和非阻塞这两个术语是密切相关的,通常可以互换使用,但它们并不完全相同。

舞台调度

函数 阻碍 当它在返回前等待某件事情发生时。一个函数可能由于许多原因而阻塞:网络I/O、磁盘I/O、互斥锁等。 每一个 函数块,至少有一点,当它运行和使用CPU时(对于一个极端的例子,它演示了为什么必须像其他类型的块那样重视CPU块,考虑密码散列函数,例如 bcrypt 它设计使用数百毫秒的CPU时间,远远超过典型的网络或磁盘访问)。

函数在某些方面可以是阻塞的,而在其他方面可以是非阻塞的。在 Tornado 的背景下,我们通常在网络I/O的背景下讨论阻塞,尽管所有类型的阻塞都要最小化。

异步的

异步的 函数在完成前返回,通常会导致在后台执行某些操作,然后在应用程序中触发某些未来操作(与正常操作相反) 同步的 函数,它们在返回之前完成所有要做的事情)。异步接口有多种类型:

  • 回调参数

  • 返回占位符 (FuturePromiseDeferred

  • 传递到队列

  • 回调注册表(例如posix信号)

不管使用哪种类型的接口,异步函数 根据定义 与调用者的交互方式不同;没有可用的方法使同步函数以对调用者透明的方式异步(如 gevent 使用轻量级线程提供与异步系统相当的性能,但实际上它们并不能使事情异步)。

Tornado中的异步操作通常返回占位符对象 (Futures ,除了一些低级组件,如 IOLoop 使用回调。 Futures 通常通过 awaityield 关键词。

实例

下面是一个同步函数示例:

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 块的工作正如您在协程中所期望的那样,而使用回调很难实现这一点。协程将在本指南的下一节中深入讨论。