经常问的问题¶
为什么 time.sleep()
并行运行不是这个例子?¶
许多人第一次进入Tornado的并发看起来像这样:
class BadExampleHandler(RequestHandler):
def get(self):
for i in range(5):
print(i)
time.sleep(1)
同时获取此处理程序两次,您会看到第二个五秒倒计时不会开始,直到第一个完全完成。其原因是 time.sleep
是 阻塞 函数:它不允许控制返回到 IOLoop
,以便可以运行其他处理程序。
当然,time.sleep
实际上只是这些示例中的一个占位符,关键是显示当处理程序中的内容变慢时会发生什么。不管真正的代码是做什么的,实现并发阻塞的代码必须用非阻塞的等同替换。这意味着三件事之一:
找到一个coroutine友好的等价物。 对于
time.sleep
,请改用tornado.gen.sleep
:class CoroutineSleepHandler(RequestHandler): @gen.coroutine def get(self): for i in range(5): print(i) yield gen.sleep(1)
当这个选项可用时,它通常是最好的方法。有关可能有用的异步库的链接,请参阅 Tornado wiki。
找到一个基于回调的等效。 与第一个选项类似,基于回调的库可用于许多任务,虽然它们比为协同设计的库稍微复杂一些。这些通常与
tornado.gen.Task
一起用作适配器:class CoroutineTimeoutHandler(RequestHandler): @gen.coroutine def get(self): io_loop = IOLoop.current() for i in range(5): print(i) yield gen.Task(io_loop.add_timeout, io_loop.time() + 1)
再次,Tornado wiki 可用于寻找合适的文库。
在另一个线程上运行阻塞代码。 当异步库不可用时,
concurrent.futures.ThreadPoolExecutor
可用于在另一个线程上运行任何阻塞代码。这是一个通用的解决方案,可以用于任何阻塞函数,无论是否存在异步对等体:executor = concurrent.futures.ThreadPoolExecutor(8) class ThreadPoolHandler(RequestHandler): @gen.coroutine def get(self): for i in range(5): print(i) yield executor.submit(time.sleep, 1)
有关阻塞和异步函数的更多信息,请参阅Tornado用户指南的 异步I/O 章节。
我的代码是异步的,但它不是在两个浏览器选项卡中并行运行。¶
即使处理程序是异步和非阻塞的,验证这一点也是令人惊讶的棘手。浏览器会识别您尝试在两个不同的选项卡中加载相同的页面,并延迟第二个请求,直到第一个完成。要解决这个问题,并看到服务器实际上并行工作,请执行以下两项操作之一:
添加一些东西到你的urls,使他们独特。代替两个选项卡中的
http://localhost:8888
,在一个中加载http://localhost:8888/?x=1
,在另一个中加载http://localhost:8888/?x=2
。使用两个不同的浏览器。例如,即使在Chrome标签中加载相同的网址,Firefox也能加载网址。