【问题标题】:why my coroutine blocks whole tornado instance?为什么我的协程会阻止整个龙卷风实例?
【发布时间】:2013-09-26 05:49:24
【问题描述】:
from tornado import web, gen
import tornado, time

class CoroutineFactorialHandler(web.RequestHandler):
    @web.asynchronous
    @gen.coroutine
    def get(self, n, *args, **kwargs):
        n = int(n)
        def callbacker(iterator, callback):
            try:
                value = next(iterator)
            except StopIteration:
                value = StopIteration
            callback(value)

        def factorial(n):
            x = 1
            for i in range(1, n+1):
                x *= i
                yield

            yield x

        iterator = factorial(n)
        t = time.time()
        self.set_header("Content-Type", "text/plain")
        while True:
            response = yield gen.Task(callbacker, iterator)
            #log.debug("response: %r" %response)
            if response is StopIteration:
                break
            elif response:
                self.write("took : %f sec" %(time.time() - t))
                self.write("\n")
                self.write("f(%d) = %d" %(n, response))

        self.finish()

application = tornado.web.Application([
    (r"^/coroutine/factorial/(?P<n>\d+)", CoroutineFactorialHandler),
    #http://localhost:8888/coroutine/factorial/<int:n>
])

if __name__ == "__main__":
    application.listen(8888)
    ioloop = tornado.ioloop.IOLoop.instance()
    ioloop.start()

21 行被拉出 以上是简单的阶乘计算器。它以生成器的方式循环 N 次。

问题是,当这段代码执行时,它会阻塞整个龙卷风。

我想要实现的是为 tornado 编写一些帮助程序,将生成器视为协程,因此可以以异步方式服务请求。 (我已阅读Using a simple python generator as a co-routine in a Tornado async handler?

为什么简单的增加和乘以 n 循环会阻止整个龙卷风?

edit :我编辑了代码以包含整个应用程序,您可以运行和测试它。 我在 python 2.7 上运行 tornado 3.1.1

【问题讨论】:

  • 你的get 真的可以接受这样的论点吗? (当我在 Python 2.7.2 上使用 Tornado 3.1.1 尝试此操作时,我得到一个 TypeError: get() takes at least 2 arguments (1 given)。我认为这不是你的问题——如果我将其更改为不使用 args 并使用 self.get_argument(n),我想无论如何它都说明了你的问题。但我不确定。那么,这实际上是你的代码吗?如果是,你使用的是哪个版本?
  • @abarnert 我编辑了代码。如果您仍然感兴趣,请看一下。
  • 啊,我明白了,您想使用路径组件,而不是查询字符串。说得通。无论如何,我认为这不是你的问题——正如我所说,我使用self.get_argument 读取查询字符串的编辑版本展示了相同的行为。我没有答案给你。有机会我会仔细研究一下,但希望其他比我使用 Tornado 的人会先出现。

标签: python asynchronous tornado coroutine


【解决方案1】:

您必须记住 Tornado 在一个线程中运行。代码被拆分为在主循环中顺序调用的任务。如果其中一项任务需要很长时间才能完成(因为像 time.sleep() 这样的阻塞函数或像阶乘这样的繁重计算),它会阻塞整个循环。

那么你能做什么...?一种解决方案是使用IOLoop.add_callback() 创建循环:

from tornado import web, gen
import tornado, time

class CoroutineFactorialHandler(web.RequestHandler):
    def factorial(self, limit=1):
        count = 1
        fact = 1
        while count <= limit:
            yield fact
            count = count + 1
            fact = fact * count 

    def loop(self):
        try:
            self.fact = self.generator.next()
            tornado.ioloop.IOLoop.instance().add_callback(self.loop)
        except StopIteration:
            self.write("took : %f sec" %(time.time() - self.t))
            self.write("\n")
            self.write("f(%d) = %d" % (self.n, self.fact))
            self.finish()

    @web.asynchronous
    def get(self, n, *args, **kwargs):
        self.n = int(n)
        self.generator = self.factorial(self.n)
        self.t = time.time()
        self.set_header("Content-Type", "text/plain")
        tornado.ioloop.IOLoop.instance().add_callback(self.loop)

application = tornado.web.Application([
    (r"^/coroutine/factorial/(?P<n>\d+)", CoroutineFactorialHandler),
    #http://localhost:8888/coroutine/factorial/<int:n>
])

if __name__ == "__main__":
    application.listen(8888)
    ioloop = tornado.ioloop.IOLoop.instance()
    ioloop.start()

这里的每个乘法都是一个单独的任务,它允许混合来自不同请求的 factorial 生成器调用。如果每次调用生成器都花费相同的时间,这是一个好方法。但是,如果您要计算 100000!然后在某个时间点,按顺序排列的任务看起来像 90000!*90001、90001!*90002 等等。即使它只有一个乘法而不是整个循环,也需要一些时间来计算它,因此另一个请求将被延迟。对于如此大的输入整数,您必须在另一个线程中进行计算才能公平地分享请求的处理器时间。以下是如何执行此操作的示例:http://lbolla.info/blog/2013/01/22/blocking-tornado

附带说明,在阶乘中,您有很多冗余,因此您应该在内存中保留一些 n 的解决方案列表,以便立即将它们恢复,而不会浪费处理器时间一遍又一遍地进行相同的计算。

【讨论】:

  • 嗨,我查看了指向这个答案的博客,它使用给定最大线程数的线程池。如何配置最大线程数?如果我要将这个逻辑应用于我的门户网站的每个操作,我最终会创建许多线程......通常是大多数网络服务器所做的!
  • 请记住,您应该避免创建线程。异步编程的重点是使用单线程并在调用之间交替。您应该尝试重写代码以使其异步,使用快速计算的调用,使用异步库(Tornado 可以使用 Twisted 库)或使用 JavaScript 将部分工作移动到客户端(上面的答案中的代码只是一个示例,因为客户端的浏览器可以做到这一点)。应该避免创建线程,因为它会让您回到 Tornado 让您避免的所有线程问题,例如您的问题中的一个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-05
  • 2011-10-27
  • 1970-01-01
  • 2013-09-13
相关资源
最近更新 更多