【问题标题】:Having problems running a long blocking function in a thread in Tornado在 Tornado 的线程中运行长阻塞函数时遇到问题
【发布时间】:2013-10-19 23:51:37
【问题描述】:

我对 Tornado 很陌生。只是在看看如何处理在 Tornado 中阻塞的请求。我在单独的线程中运行阻塞代码。然而,主线程仍然阻塞,直到线程函数完成。我这里没有使用 gen.coroutine,但是试过了,结果是一样的

counter = 0

def run_async(func):
    @wraps(func)
    def function_in_a_thread(*args, **kwargs):
        func_t = Thread(target=func, args=args, kwargs=kwargs)
        func_t.start()
    return function_in_a_thread

def long_blocking_function(index, sleep_time, callback):
    print "Entering run counter:%s" % (index,)
    time.sleep(sleep_time)
    print "Exiting run counter:%s" % (index,)

    callback('keyy' + index)


class FooHandler(tornado.web.RequestHandler):

    @web.asynchronous
    def get(self):

        global counter
        counter += 1
        current_counter = str(counter)

        print "ABOUT to spawn thread for counter:%s" % (current_counter,)
        long_blocking_function(
            index=current_counter,
            sleep_time=5, callback=self.done_waiting)


        print "DONE with the long function"

    def done_waiting(self, response):
        self.write("Whatever %s " % (response,))
        self.finish()


class Application(tornado.web.Application):
    def __init__(self):
        handlers = [(r"/foo", FooHandler),
                    ]

        settings = dict(
            debug=True,
        )

        tornado.web.Application.__init__(self, handlers, **settings)


def main():
    application = Application()
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

当我发出背靠背请求时,FooHandler 会阻塞并且在 long_blocking_function 完成之前不会收到任何请求。所以我最终看到了类似的东西

ABOUT to spawn thread for counter:1
Entering run counter:1
Exiting run counter:1
DONE with the long function
ABOUT to spawn thread for counter:2
Entering run counter:2
Exiting run counter:2
DONE with the long function
ABOUT to spawn thread for counter:3
Entering run counter:3
Exiting run counter:3
DONE with the long function

我期待这些方面的东西(因为我在第一次调用 long_blocking_function 完成之前发出多个请求)但我只看到类似于上面的跟踪

ABOUT to spawn thread for counter:1
DONE with the long function
ABOUT to spawn thread for counter:2
DONE with the long function
ABOUT to spawn thread for counter:3
DONE with the long function
ABOUT to spawn thread for counter:4
DONE with the long function

我查看了Tornado blocking asynchronous requests 并尝试了这两种解决方案。但是当我使用对同一个处理程序的背靠背请求运行它们时,它们都被阻塞了。有人能弄清楚我做错了什么吗?我知道龙卷风在多线程方面表现不佳,但我应该能够以非阻塞方式从中运行一个新线程。

【问题讨论】:

    标签: python multithreading tornado


    【解决方案1】:

    您只是忘记实际使用您定义的 run_async 装饰器。

    import tornado.web
    import functools
    import threading
    import time
    
    counter = 0
    def run_async(func):
        @functools.wraps(func)
        def function_in_a_thread(*args, **kwargs):
            func_t = threading.Thread(target=func, args=args, kwargs=kwargs)
            func_t.start()
        return function_in_a_thread
    
    
    @run_async
    def long_blocking_function(index, sleep_time, callback):
        print ("Entering run counter:%s" % (index,))
        time.sleep(sleep_time)
        print ("Exiting run counter:%s" % (index,))
        callback('keyy' + index)
    
    class FooHandler(tornado.web.RequestHandler):
        @tornado.web.asynchronous
        def get(self):
            global counter
            counter += 1
            current_counter = str(counter)
    
            print ("ABOUT to spawn thread for counter:%s" % (current_counter,))
            long_blocking_function(
                index=current_counter,
                sleep_time=5, callback=self.done_waiting)
            print ("DONE with the long function")
    
        def done_waiting(self, response):
            self.write("Whatever %s " % (response,))
            self.finish()
    
    
    class Application(tornado.web.Application):
        def __init__(self):
            handlers = [(r"/foo", FooHandler),
                        ]
    
            settings = dict(
                debug=True,
            )
    
            tornado.web.Application.__init__(self, handlers, **settings)
    
    
    def main():
        application = Application()
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    
    if __name__ == "__main__":
        main()
    

    【讨论】:

    • 这不是问题所在。我正在使用 @run_async 装饰器,只是忘记将它复制到这里。看起来它一直运行良好,但浏览器正在排队请求。现在根据 Rod 的建议使用 curl
    【解决方案2】:

    当您将任务卸载到另一个线程时,您会在事件循环中注册一个回调,该回调会在任务完成后调用。答案在这个帖子里:How to make a library asynchronous in python

    问候 米。

    【讨论】:

    • 感谢您的回复。这真的很有帮助。基于此,我现在将回调添加到非主线程完成时调用的 ioloop,但它仍然阻止新请求。仅在先前请求的异步工作线程完成后才处理新请求。这是 FooHandler 代码的link
    • 我不知道为什么会发生这种情况 - 乍一看它应该可以完成这项工作。我的一个想法是,在请求中实例化的线程池可能会导致问题。也许这会阻塞该方法,直到启动的工作线程终止(由 executor 变量的待处理垃圾收集引起。
    • 一切正常。是我的浏览器在排队请求。不过感谢您的帮助。 lbolla's article 是一本不错的读物,但认为龙卷风的推荐方式现在是协同程序而不是回调
    【解决方案3】:

    Tornado 与 concurrent.futures 库配合得很好(有一个 Python 2.x backport 可用),因此您可以使用 ThreadPoolExecutor 将长时间运行的请求移交给线程池。

    这种技术效果很好——我们用它来处理长时间运行的数据库操作。当然,在现实世界中,您还希望以稳健和优雅的方式处理超时和其他异常,但我希望这个示例足以说明这个想法。

    def long_blocking_function(index, sleep_time, callback):
        print ("Entering run counter:%s" % (index,))
        time.sleep(sleep_time)
        print ("Exiting run counter:%s" % (index,))
        return "Result from %d" % index
    
    
    class FooHandler(tornado.web.RequestHandler):
        @tornado.gen.coroutine
        def get(self):
            global counter
            counter += 1
            current_counter = str(counter)
    
            print ("ABOUT to spawn thread for counter:%s" % (current_counter,))
            result = yield self.executor.submit(long_blocking_function,
                                                index=current_counter,
                                                sleep_time=5)
            self.write(result)
            print ("DONE with the long function")
    

    【讨论】:

    • 谢谢罗德。我正在尝试这个。理想情况下,我想使用 gen.coroutine 而不是获取和回调函数。但这会阻塞。当我发出背靠背请求时,每个请求都会阻塞,直到前一个请求的异步任务完成。这是BarHandler based on coroutines 代码。
    • 冒昧forking your gist。我已经使用 curl 对其进行了测试,它按预期工作。
    • 成功了!!谢谢罗德。浏览器正在排队请求。它适用于 curl。让 FooHandler 和 BarHandler 在此基础上工作。
    • 在执行器调用中不使用yield会有什么影响?我有类似的情况,如果我使用yield,我的程序会崩溃,但没有它它可以正常运行......请参阅here
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多