3

我有一个需要一段时间才能运行的函数,并且需要在请求中运行它。处理此问题的最佳方法是什么,以便此请求在处理时不会阻塞主线程?我查看了@tornado.web.asynchronous装饰器,但是当该函数不是异步龙卷风模块时,这里用处不大。

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

    def long_time_function(self):
        time.sleep(5)
        return "foo"
4

2 回答 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")

如果你想自己做,这个要点展示了一个你必须做的例子——或者,当然,各种 Tornado 线程池库的源代码可以作为示例代码。

请记住 Python GIL 的局限性:如果您的后台任务受 CPU 限制(并且大部分工作在 Python 中完成,而不是在像 numpy 那样释放 GIL 的 C 扩展中),您必须将它放在单独的过程。快速搜索 Tornado 进程池库并没有找到那么多不错的选择,但是在 Python 中使线程池代码适应进程池代码通常非常容易。**


* 请注意,我并不是特别推荐该库;这只是谷歌搜索中出现的第一件事,一眼看去,它看起来可用且正确。

** 它通常就像concurrent.futures.ThreadPoolExecutorconcurrent.futures.ProcessPoolExecutormultiprocessing.dummy.Pool替换一样简单multiprocessing.Pool。唯一的诀窍是确保所有任务参数和返回值都小且可腌制。

于 2013-10-31T21:29:57.393 回答
1

使用其他答案中所述的另一个线程是一种方法。如果您的函数可以拆分为片段(例如,内部有一个包含许多迭代的循环),您也可以将其拆分为IOLoop.add_callback()将计算与处理其他请求交织在一起。这是一个如何执行此操作的示例:为什么我的协程会阻塞整个龙卷风实例?

于 2013-10-31T21:36:01.650 回答