Asynchronous and non-Blocking I/O

实时Web功能要求每个用户建立长期存在的大部分空闲连接. 在传统的同步Web服务器中,这意味着为每个用户分配一个线程,这可能非常昂贵.

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

术语"异步"和"非阻塞"密切相关,通常可以互换使用,但它们并非完全相同.

Blocking

函数在返回之前等待发生的事情时会阻塞 . 一个功能可能由于多种原因而被阻塞:网络I / O,磁盘I / O,互斥锁等.实际上, 每个功能在运行和使用CPU时都会阻塞至少一点点(例如,一个极端的例子为什么必须与其他类型的阻止一样重视CPU阻止,请考虑使用密码哈希函数(如bcrypt) ,该函数在设计上会占用数百毫秒的CPU时间,远远超过了典型的网络或磁盘访问时间.

函数在某些方面可以是阻塞的,而在其他方面则可以是非阻塞的. 在Tornado的上下文中,我们通常谈论在网络I / O上下文中的阻塞,尽管所有类型的阻塞都将被最小化.

Asynchronous

异步函数会在完成之前返回,通常会导致一些工作在后台发生,然后再触发应用程序中的某些将来的动作(与普通的同步函数相反,后者在返回之前会做所有的事情). 异步接口有很多样式:

  • 回调参数

  • 返回一个占位符( FuturePromiseDeferred

  • 传递到队列

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

无论使用哪种类型的接口, 根据定义 ,异步函数与其调用方的交互方式都不同. 没有任何免费方法可以使同步函数对其调用者透明(例如gevent之类的系统使用轻量级线程来提供与异步系统相当的性能,但实际上并不能使它们异步).

龙卷风中的异步操作通常返回占位符对象( Futures ),除了一些使用回调的底层组件(如IOLoop . Futures通常使用awaityield关键字转换为结果.

Examples

这是一个示例同步功能:

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

Notice that the coroutine returns its Future before the fetch is done. This is what makes coroutines asynchronous.

协程可以做的任何事情,都可以通过传递回调对象来完成,但是协程通过让您以与同步代码相同的方式组织代码来提供重要的简化. 这对于错误处理尤为重要,因为try / except块可以像在协程中所期望的那样工作,而使用回调很难做到这一点. 协程将在本指南的下一部分中进行深入讨论.