tornado是异步Web服务器吗?

0 投票
2 回答
687 浏览
提问于 2025-04-19 03:40

我正在学习如何写一个可以处理成千上万连接的后台服务器。

我看了一些示例代码,但发现它们还是用的同步逻辑。

比如说: (摘自 http://www.tornadoweb.org/en/stable/gen.html)

@gen.coroutine
def get(self):
    http_client = AsyncHTTPClient()
    response1, response2 = yield [http_client.fetch(url1), http_client.fetch(url2)]
    print(response1.body, response2.body)

很明显,print 语句在获取两个请求的响应之前是无法执行的,否则会因为访问不存在的数据而抛出异常。

因此,在最后两行之间必须有一个阻塞,但是,阻塞不是tornado的特点,它强调的是非阻塞、异步和事件驱动……那么,它是怎么处理成千上万的连接的呢?

2 个回答

-1

没错!

在处理长时间运行的任务和慢速输入输出(IO)操作时,使用 yieldcoroutine 这两种方式很容易让人混淆。我刚刚在这方面犯了一个大错误。

  • 在长时间运行的任务中

    1. 每次都会重复调用生成器的 next() 方法,并执行一小部分工作。
    2. 如果有多个 coroutine 同时运行,调度器会一个一个地调用每个 next() 方法,这样就能在这些任务之间共享 CPU 时间。因此,这些任务之间是合作的,所以叫做 coroutine
  • 在慢速 IO 操作中

    1. 每个 yield 点只会调用一次 next() 方法。
    2. 一旦在进行 IO 操作时遇到 yield,这个 IO 操作就交给操作系统内核处理。调度器会在 IO 操作完成后添加一个回调,这个回调会调用 next() 方法。
    3. 现在,调度器要决定什么时候调用 next() 方法。这是由操作系统级别的异步功能来实现的,比如 epollIOCP,它们会通知调度器 IO 操作何时完成。
    4. 整个流程是,运行到进行 IO 的那一点,然后 yield 以交出执行权。IO 完成后,调度器会通过调用 next() 方法继续执行。
    5. 这种控制流的效果和回调模式是完全一样的,都是:
      • 运行到进行 IO 的那一点
      • 交出执行权,让进程可以做其他工作
      • IO 完成后,继续运行。
      • 唯一的区别是,一个是在原来的函数中继续执行,另一个是继续到一个新的回调函数。

所以,总结一下:

  • 在长时间运行的任务中,调度器会在进程空闲时调用每个 next() 方法。

  • 在慢速 IO 操作中,next() 方法只会调用一次,当 IO 操作完成时。

我认为如果你明白这一点,就会理解使用 yieldcoroutine 实际上可以和 callback 有相同的效果。

3

没错,tornado 是异步的。你提到的例子是一个 coroutine(协程);它实际上是非阻塞的,并且在调用 yield 时会把控制权交回给 tornado 的事件循环。只有当两个 http_client.fetch 的调用都完成后,控制权才会返回到 get 函数。

这两个例子在 tornado 中实际上是功能上等价的:

class AsyncHandler(RequestHandler):
    @asynchronous
    def get(self):
        http_client = AsyncHTTPClient()
        http_client.fetch("http://example.com",
                          callback=self.on_fetch)

    def on_fetch(self, response):
        do_something_with_response(response)
        self.render("template.html")

还有一个协程版本:

class GenAsyncHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        http_client = AsyncHTTPClient()
        response = yield http_client.fetch("http://example.com")
        do_something_with_response(response)
        self.render("template.html")

协程让你可以写出看起来像是同步的异步代码,这样更容易阅读。当上面的代码执行到 yield 时,get 会暂停,并把 http_client.fetch 返回的 Future 对象交给 gen.coroutine 装饰器。这个 gen.coroutine 装饰器有一些神奇的功能,它会安排在 fetch 调用返回的 Future 准备好后,再把结果传回给 get

撰写回答