- 摘要:异步和非阻塞I/O实时WEB的特性是经常需要为每个用户端维持一个长时间存活但是大部分时候空闲的连接。在传统的同步式web服务器中,这主要通过为每个用户创建一个线程来实现,这样的代价是十分昂贵的。为了最大限度地减少并发成本,Tornado使用单线程的事件循环机制(linux中是基于epoll的).这就意味着所有的应用代码都应该是异步或非阻塞的,因为同时只能有一个操作是活动的。尽管异步和非阻塞这2个术语是密切相关的,并且通常可以互换使用,但并不完全相同。阻塞当一个函数在等待某些事
-
异步和非阻塞I/O
实时WEB的特性是经常需要为每个用户端维持一个长时间存活但是大部分时候空闲的连接。在传统的同步式web服务器中,这主要通过为每个用户创建一个线程来实现,这样的代价是十分昂贵的。
为了最大限度地减少并发成本,Tornado使用单线程的事件循环机制(linux中是基于epoll的).这就意味着所有的应用代码都应该是异步或非阻塞的,因为同时只能有一个操作是活动的。
尽管异步和非阻塞这2个术语是密切相关的,并且通常可以互换使用,但并不完全相同。
阻塞
当一个函数在等待某些事件的时候就会阻塞。一个函数阻塞的原因有很多:网络I/O,磁盘I/O,锁等等。
实际上,所有的函数在使用CPU的时候,都多多少少的会阻塞,至少会有一点点。
下面有一个极端的例子演示了为什么CPU阻塞比其他类型的阻塞还要严重。
密码哈希函数比如bcrypt,被设计为要使用数百ms的CPU时间,远远大于通常的网络或磁盘访问。
一个函数可以在某些方面阻塞,也可以在某些方面非阻塞。
比如,在默认配置下,tornado.httpclient会在DNS解析时阻塞但是不会在其他网络访问时阻塞(为了缓解这种状况,可以使用ThreadResolver和tornado.curl_httpclient
它们是通过合适的配置用libcurl来构建的)
在tornado的背景中,我们一般只讨论网络I/O下的阻塞,尽管其它类型的阻塞也被最小化了。
异步
一个异步函数在它完成之前返回,在应用程序触发一些未来的动作之前,需要在后台做一些工作。这与同步函数正好相反,同步函数在返回之前把所有要做的事件都做完。
有许多类型的异步接口:
1.回调函数参数
2.返回一个占位符(Future,Promise,Deferred)
3.发送到队列
4.回调函数注册(比如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是比较复杂的,但是Tornado无条件地建议在实际编程中使用Future,因为有以下2点好处:
1.使错误处理更加一致,因为Future.result方法可以简单地抛出一个异常
2.Future很好地使用了coroutines.Coroutines将在下一节进行更深的讨论。
下面是一个coroutine版本的异步函数,与最开始的同步函数十分相似:
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)语句是一个包装在python2和python3.2里,因为python的这些版本不支持将生成器函数作为返回值。
为了克服这个,Tornado coroutines抛出一个特殊种类的异常,称为Return.
coroutine捕获这个异常并将它当作一个返回值。
在Python3.3和之后的版本里面,使用"return response.body"的效果是一样的。