Skip to main content

异步和非阻塞I/O

实时网络功能需要每个用户的长期大多数空闲连接。在传统的同步Web服务器中,这意味着向每个用户分配一个线程,这可能是非常昂贵的。

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

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

阻塞

函数 当它等待事情发生之前返回。一个函数可能阻塞的原因有很多:网络I/O,磁盘I/O,互斥等。事实上,every 功能块,至少有一点点,而它正在运行和使用CPU(对于一个极端的例子,为什么CPU阻塞必须像其他类型的阻塞一样严重,认为密码哈希函数像 bcrypt,其设计使用几百毫秒的CPU时间,远远超过典型的网络或磁盘访问)。

函数在某些方面可以是阻塞,在其他方面可以是非阻塞。例如,默认配置中的 tornado.httpclient 阻止DNS解析,但不阻止其他网络访问(以减轻此使用 ThreadedResolver 或具有正确配置的 libcurl 构建的 tornado.curl_httpclient)。在Tornado的上下文中,我们通常讨论在网络I/O的上下文中阻塞,尽管所有类型的阻塞都被最小化。

异步

异步 函数在完成之前返回,并且通常在触发应用程序中的某个未来动作之前在后台中发生一些工作(与正常的 同步 函数相反,在正常的 同步 函数中,它们在返回之前做所有的事情)。有很多种异步接口:

  • 回调参数

  • 返回占位符(FuturePromiseDeferred

  • 传送到队列

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

不管使用哪种类型的接口,异步函数 按定义 与它们的调用者进行不同的交互;没有自由的方式使同步函数以对其调用者透明的方式异步(像 gevent 这样的系统使用轻量级线程来提供与异步系统相当的性能,但它们实际上并不使异步)。

例子

这里是一个示例同步函数:

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

def asynchronous_fetch(url, callback):
    http_client = AsyncHTTPClient()
    def handle_response(response):
        callback(response.body)
    http_client.fetch(url, callback=handle_response)

再次用 Future 而不是回调:

from tornado.concurrent import Future

def async_fetch_future(url):
    http_client = AsyncHTTPClient()
    my_future = Future()
    fetch_future = http_client.fetch(url)
    fetch_future.add_done_callback(
        lambda f: my_future.set_result(f.result()))
    return my_future

原始 Future 版本更复杂,但是 Futures 仍然是Tornado的推荐做法,因为它们有两个主要优点。错误处理更一致,因为 Future.result 方法可以简单地引发异常(而不是在面向回调的接口中常见的自组织错误处理),并且 Futures 很好地使用协同程序。协程将在本指南的下一部分中深入讨论。这里是我们的示例函数的协程版本,它非常类似于原始的同步版本:

from tornado import gen

@gen.coroutine
def fetch_coroutine(url):
    http_client = AsyncHTTPClient()
    response = yield http_client.fetch(url)
    raise gen.Return(response.body)

语句 raise gen.Return(response.body) 是Python 2的工件,其中生成器不允许返回值。为了克服这个问题,Tornado协程提出了一种特殊的异常,称为 Return。协同程序捕获此异常并将其视为返回值。在Python 3.3及更高版本中,return response.body 实现了相同的结果。