异步和非阻塞 I/O¶

实时的web特性通常需要为每个用户一个大部分时间都处于空闲的长连接.在传统的同步web服务器中,这意味着需要给每个用户分配一个专用的线程,这样的开销是十分巨大的.

为了减小对于并发连接需要的开销,Tornado使用了一种单线程事件循环的方式.这意味着所有应用程序代码都应该是异步和非阻塞的,因为在同一时刻只有一个操作是有效的.

异步和非阻塞这两个属于联系十分紧密而且通常交换使用,但是它们并不完全相同

阻塞¶

一个函数通常在它等待返回值的时候被 阻塞 .一个函数被阻塞可能由于很多原因:网络I/O,磁盘I/O,互斥锁等等.事实上, 每一个 函数都会被阻塞,只是时间会比较短而已,当一个函数运行时并且占用CPU(举一个极端的例子来说明为什么CPU阻塞的时间必须考虑在内,考虑以下密码散列函数像bcrypt, 这个函数需要占据几百毫秒的CPU时间,远远超过了通常对于网络和磁盘请求的时间).

一个函数可以在某些方面阻塞而在其他方面不阻塞.举例来说, tornado.httpclient在默认设置下将阻塞与DNS解析,但是在其它网络请求时不会阻塞(为了减轻这种影响,可以用 ThreadedResolver 或通过正确配置 libcurl 使用tornado.curl_httpclient ).在Tornado的上下文中我们通常讨论网络I/O上下文阻塞,虽然各种阻塞已经被最小化了.

异步¶

一个 异步 函数在它结束前就已经返回了,而且通常会在程序中触发一些动作然后在后台执行一些任务.(和正常的 同步 函数相比, 同步函数在返回之前做完了所有的事). 这里有几种类型的异步接口:

  • 回调函数
  • 返回一个占位符 (Future, Promise, Deferred)
  • 传送一个队列
  • 回调注册 (例如. POSIX 信号)
    不论使用哪一种类型的接口, 依据定义 异步函数与他们的调用者有不同的交互方式;但没有一种对调用者透明的方式可以将同步函数变成异步函数 (像 gevent 通过一种轻量的线程库来提供异步系统,但是实际上它并不能让事情变得异步)

示例¶

一个简单的同步函数:

  1. from tornado.httpclient import HTTPClient
  2.  
  3. def synchronous_fetch(url):
  4. http_client = HTTPClient()
  5. response = http_client.fetch(url)
  6. return response.body

这时同样的函数但是被通过回调参数方式的异步方法重写了:

  1. from tornado.httpclient import AsyncHTTPClient
  2.  
  3. def asynchronous_fetch(url, callback):
  4. http_client = AsyncHTTPClient()
  5. def handle_response(response):
  6. callback(response.body)
  7. http_client.fetch(url, callback=handle_response)

再一次 通过 Future 替代回调函数:

  1. from tornado.concurrent import Future
  2.  
  3. def async_fetch_future(url):
  4. http_client = AsyncHTTPClient()
  5. my_future = Future()
  6. fetch_future = http_client.fetch(url)
  7. fetch_future.add_done_callback(
  8. lambda f: my_future.set_result(f.result()))
  9. return my_future

原始的 Future 版本十分复杂, 但是 Futures 是 Tornado 中推荐使用的一种做法,因为它有两个主要的优势. 错误处理时通过Future.result 函数可以简单的抛出一个异常 (不同于某些传统的基于回调方式接口的一对一的错误处理方式), 而且 Futures 对于携程兼容的很好. 协程将会在本篇的下一节详细讨论. 这里有一个协程版本的实力函数, 这与传统的同步版本十分相似.

  1. from tornado import gen
  2.  
  3. @gen.coroutine
  4. def fetch_coroutine(url):
  5. http_client = AsyncHTTPClient()
  6. response = yield http_client.fetch(url)
  7. raise gen.Return(response.body)

语句 raise gen.Return(response.body) 在 Python 2 中是人为设定的,因为生成器不允许又返回值. 为了克服这个问题, Tornado 协程抛出了一个叫做 Return 的特殊异常.协程将会像返回一个值一样处理这个异常.在 Python 3.3+ 中, return
response.body
将会达到同样的效果.

原文:

https://tornado-zh-cn.readthedocs.io/zh_CN/latest/guide/async.html