Tornado非阻塞请求

3

我有一个运行时间较长的函数,需要在请求中运行。如何处理才能使该请求在处理过程中不会阻塞主线程?我查看了@tornado.web.asynchronous修饰符,但是当该函数不是异步Tornado模块时,这并没有太大用处。

class LongHandler(tornado.web.RequestHandler):
    def get(self):
        self.write(self.long_time_function())

    def long_time_function(self):
        time.sleep(5)
        return "foo"
2个回答

7
当您有一些阻塞任务与异步事件循环不兼容时,您需要将其放在单独的线程中。
如果您将拥有无限数量的阻塞任务,则需要使用线程池。
无论哪种方式,您都需要具有包装器异步任务,该任务在来自线程化任务的通知上阻塞。
最简单的方法是使用预构建的库,例如tornado-threadpool。*然后,您只需执行以下操作:
class LongHandler(tornado.web.RequestHandler):
    @thread_pool.in_thread_pool
    def long_time_function(self, callback):
        time.sleep(5)
        callback("foo")

如果你想自己动手,this gist提供了一个示例,告诉你该怎么做——或者当然,各种Tornado线程池库的源代码也可以作为示例代码。
只要记住Python GIL的限制:如果你的后台任务是CPU绑定的(大部分工作在Python中完成,而不是在像numpy这样释放GIL的C扩展中),你必须将其放在单独的进程中。对于Tornado进程池库的快速搜索没有找到太多好的选择,但在Python中将线程池代码改为进程池代码通常非常容易。
注意,我并不是特别推荐这个库;它只是在谷歌搜索中出现的第一个东西,从快速浏览中看起来可用和正确。
通常只需将concurrent.futures.ThreadPoolExecutor替换为concurrent.futures.ProcessPoolExecutor或将multiprocessing.dummy.Pool替换为multiprocessing.Pool。唯一的技巧是确保所有任务参数和返回值都很小且可pickle化。

1

如其他答案所述,使用另一个线程是一种解决方法。如果您的函数可以分成若干片段(例如,其中有一个包含许多迭代的循环),您还可以使用 IOLoop.add_callback() 进行拆分,以便将其计算与处理其他请求交织在一起。下面是一个示例:为什么我的协程会阻塞整个tornado实例?


谢谢,我看到另一个类似策略的问题/答案,但在这种情况下,我无法控制函数的工作方式;我只是调用它。 - Leagsaidh Gordon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接