Tornado 异步和非阻塞 I/O
异步和非阻塞 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通常使用awaitoryield 关键字转换成它们的结果。
演示例子
这是一个同步函数:
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
在 fetch 完成之前返回它。这就是使协同程序异步的原因。
任何你可以用协程做的事情,你也可以通过传递回调对象来做,但是协程提供了一个重要的简化,它让你以与同步时相同的方式组织你的代码。这对于错误处理尤其重要,因为try
/except
块的工作方式与您在协程中所期望的一样,而这很难通过回调实现。本指南的下一部分将深入讨论协程