常见问题解答¶
为什么这个包含 time.sleep()
的例子不能并行运行?¶
许多人第一次尝试 Tornado 的并发会是这样的
class BadExampleHandler(RequestHandler):
def get(self):
for i in range(5):
print(i)
time.sleep(1)
同时获取此处理程序两次,你会发现第二个五秒倒计时要等到第一个完全完成才会开始。原因是 time.sleep
是一个**阻塞**函数:它不允许控制权返回到 IOLoop
,以便运行其他处理程序。
当然,time.sleep
在这些例子中只是一个占位符,重点是展示当处理程序中的某些内容变慢时会发生什么。无论实际代码在做什么,为了实现并发,必须用非阻塞等效项替换阻塞代码。这意味着以下三种情况之一
找到一个协程友好的等效项。 对于
time.sleep
,使用tornado.gen.sleep
(或asyncio.sleep
)class CoroutineSleepHandler(RequestHandler): async def get(self): for i in range(5): print(i) await gen.sleep(1)
当这个选项可用时,它通常是最好的方法。参见 Tornado wiki,了解可能对您有用的异步库的链接。
找到一个基于回调的等效项。 与第一个选项类似,许多任务都提供基于回调的库,尽管它们的使用比专为协程设计的库要复杂一些。将基于回调的函数改造成一个 future
class CoroutineTimeoutHandler(RequestHandler): async def get(self): io_loop = IOLoop.current() for i in range(5): print(i) f = tornado.concurrent.Future() do_something_with_callback(f.set_result) result = await f
同样,Tornado wiki 可以帮助您找到合适的库。
在另一个线程上运行阻塞代码。 当异步库不可用时,
concurrent.futures.ThreadPoolExecutor
可用于在另一个线程上运行任何阻塞代码。这是一个通用的解决方案,可用于任何阻塞函数,无论是否存在异步对应项。class ThreadPoolHandler(RequestHandler): async def get(self): for i in range(5): print(i) await IOLoop.current().run_in_executor(None, time.sleep, 1)
参见 Tornado 用户指南中的 异步 I/O 章节,了解有关阻塞和异步函数的更多信息。
我的代码是异步的。为什么它在两个浏览器选项卡中不能并行运行?¶
即使处理程序是异步的且非阻塞的,验证这一点也可能出乎意料地棘手。浏览器会识别出您试图在两个不同的选项卡中加载同一个页面,并延迟第二个请求,直到第一个完成。为了绕过这个问题并查看服务器实际上是否在并行工作,可以执行以下两项操作之一
在您的 URL 中添加一些内容以使其唯一。在两个选项卡中不使用
https://127.0.0.1:8888
,而是在一个选项卡中加载https://127.0.0.1:8888/?x=1
,在另一个选项卡中加载https://127.0.0.1:8888/?x=2
。使用两个不同的浏览器。例如,Firefox 能够加载 URL,即使 Chrome 选项卡中正在加载同一个 URL。