为什么我的协程阻止整个龙卷风实例?

时间:2013-09-20 18:32:31

标签: python asynchronous tornado coroutine

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次。

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

我想要实现的是为龙卷风编写一些帮助器,将生成器视为协程,因此可以异步方式处理请求。 (我看过Using a simple python generator as a co-routine in a Tornado async handler?

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

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

1 个答案:

答案 0 :(得分:8)

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

那你可以做什么......?一种解决方案是使用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

作为旁注,在factorial中你有很多冗余,所以你应该保留内存中某些n的解决方案列表,以便立即将它们关闭,而不会浪费处理器时间来反复进行相同的计算。