Skip to main content

经常问的问题

为什么 time.sleep() 并行运行不是这个例子?

许多人第一次进入Tornado的并发看起来像这样:

class BadExampleHandler(RequestHandler):
    def get(self):
        for i in range(5):
            print(i)
            time.sleep(1)

同时获取此处理程序两次,您会看到第二个五秒倒计时不会开始,直到第一个完全完成。其原因是 time.sleep阻塞 函数:它不允许控制返回到 IOLoop,以便可以运行其他处理程序。

当然,time.sleep 实际上只是这些示例中的一个占位符,关键是显示当处理程序中的内容变慢时会发生什么。不管真正的代码是做什么的,实现并发阻塞的代码必须用非阻塞的等同替换。这意味着三件事之一:

  1. 找到一个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

  2. 找到一个基于回调的等效。 与第一个选项类似,基于回调的库可用于许多任务,虽然它们比为协同设计的库稍微复杂一些。这些通常与 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 可用于寻找合适的文库。

  3. 在另一个线程上运行阻塞代码。 当异步库不可用时,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也能加载网址。