1、tornado是单线程的,同时WSGI应用又是同步的,如果我们使用Tornado启动WSGI应用,理论上每次只能处理一个请求都是,任何一个请求有阻塞,都会导致tornado的整个IOLOOP阻塞。如下所示,我们同时发出两个GET请求向http://127.0.0.1:5000/
会发现第一个发出的请求会在大约5s之后返回,而另一个请求会在10s左右返回,我们可以判断,这两个请求是顺序执行的。
from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoopfrom flask import Flask import time
app = Flask(__name__)
@app.route('/')
def index():
time.sleep(5) return 'OK'
if __name__ == '__main__':
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(5000)
IOLoop.instance().start()
2、我们知道,tornado实现异步运行同步函数,我们只能使用线程来运行,如下所示:
import tornado.web
import tornado.ioloop
import time
import tornado
class IndexHandler(tornado.web.RequestHandler):
"""主路由处理类"""
@tornado.gen.coroutine
def get(self):
"""对应http的get请求方式"""
loop = tornado.ioloop.IOLoop.instance()
yield loop.run_in_executor(None,self.sleep)
self.write("Hello You!")
def sleep(self):
time.sleep(5)
self.write('sleep OK')
if __name__ == "__main__":
app = tornado.web.Application([
(r"/", IndexHandler),
])
app.listen(8000)
tornado.ioloop.IOLoop.current().start()
3、对于这种(使用tornado运行Flask的情况)情况,我们如何做呢,查看 WSGIContainer 的代码我们发现:
class WSGIContainer(object):
def __init__(self, wsgi_application):
self.wsgi_application = wsgi_application
def __call__(self, request):
data = {}
response = []
def start_response(status, response_headers, exc_info=None):
data["status"] = status
data["headers"] = response_headers
return response.append
# 修改这里
app_response = self.wsgi_application(
WSGIContainer.environ(request), start_response)
try:
response.extend(app_response)
body = b"".join(response)
finally:
if hasattr(app_response, "close"):
app_response.close()
if not data:
raise Exception("WSGI app did not call start_response")
status_code, reason = data["status"].split(' ', 1)
status_code = int(status_code)
headers = data["headers"]
header_set = set(k.lower() for (k, v) in headers)
body = escape.utf8(body)
if status_code != 304:
if "content-length" not in header_set:
headers.append(("Content-Length", str(len(body))))
if "content-type" not in header_set:
headers.append(("Content-Type", "text/html; charset=UTF-8"))
if "server" not in header_set:
headers.append(("Server", "TornadoServer/%s" % tornado.version))
start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
header_obj = httputil.HTTPHeaders()
for key, value in headers:
header_obj.add(key, value)
request.connection.write_headers(start_line, header_obj, chunk=body)
request.connection.finish()
self._log(status_code, request)
将 app_response 改为异步获取(使用yield)
import tornado
from tornado import escape
from tornado import httputil
from typing import List, Tuple, Optional, Callable, Any, Dict
from types import TracebackType
class WSGIContainer_With_Thread(WSGIContainer):
@tornado.gen.coroutine
def __call__(self, request):
data = {} # type: Dict[str, Any]
response = [] # type: List[bytes]
def start_response(
status: str,
headers: List[Tuple[str, str]],
exc_info: Optional[
Tuple[
"Optional[Type[BaseException]]",
Optional[BaseException],
Optional[TracebackType],
]
] = None,
) -> Callable[[bytes], Any]:
data["status"] = status
data["headers"] = headers
return response.append
loop = tornado.ioloop.IOLoop.instance()
# 修改这里
app_response = yield loop.run_in_executor(None, self.wsgi_application, WSGIContainer.environ(request),
start_response)
# app_response = self.wsgi_application(
# WSGIContainer.environ(request), start_response
# )
try:
response.extend(app_response)
body = b"".join(response)
finally:
if hasattr(app_response, "close"):
app_response.close() # type: ignore
if not data:
raise Exception("WSGI app did not call start_response")
status_code_str, reason = data["status"].split(" ", 1)
status_code = int(status_code_str)
headers = data["headers"] # type: List[Tuple[str, str]]
header_set = set(k.lower() for (k, v) in headers)
body = escape.utf8(body)
if status_code != 304:
if "content-length" not in header_set:
headers.append(("Content-Length", str(len(body))))
if "content-type" not in header_set:
headers.append(("Content-Type", "text/html; charset=UTF-8"))
if "server" not in header_set:
headers.append(("Server", "TornadoServer/%s" % tornado.version))
start_line = httputil.ResponseStartLine("HTTP/1.1", status_code, reason)
header_obj = httputil.HTTPHeaders()
for key, value in headers:
header_obj.add(key, value)
assert request.connection is not None
request.connection.write_headers(start_line, header_obj, chunk=body)
request.connection.finish()
self._log(status_code, request)
if __name__ == '__main__':
http_server = HTTPServer(WSGIContainer_With_Thread(app))
http_server.listen(5000)
IOLoop.instance().start()
注意:
1 、这种方法实际上并没有提高性能,说到底还是使用多线程来运行的,所以推荐如果使用tornado还是和tornado的web框架联合起来写出真正的异步代码,这样才会达到tornado异步IO的高性能目的,yield 在python 3.10版本不在支持协程