Tornado Coroutine - 自定义功能

时间:2016-07-01 16:46:02

标签: python asynchronous tornado

我正在理解龙卷风中的协同程序,所以让我们保持一切简单,粘贴的代码越多越好。

我想要的是让我的自制功能异步。

我在文档中找到的所有示例都属于同一个“隐藏”部分:AsyncHTTPClient。我不打算做HTTP调用。所以请不要给我这个班级的例子。我有兴趣从头开始创作。我在Tornado coroutine

上尝试了所有可能性

现在我一直在用bash睡眠进行测试。这是代码:

import tornado.web
import tornado.httpserver
import tornado.gen
import tornado.concurrent
import subprocess
import os

@tornado.gen.coroutine
def letswait():
    fut = tornado.concurrent.Future()
    subprocess.check_output(["sleep", "5"])
    fut.set_result(42)
    return fut

class TestHandler1(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        value = yield letswait()
        self.render("test.html", num=value)

class TestHandler2(tornado.web.RequestHandler):
    def get(self):
        self.render("test.html", num=66)

class Application(tornado.web.Application):
    def __init__(self):
        DIRNAME = os.path.dirname(__file__)
        STATIC_PATH = os.path.join(DIRNAME, '../static')
        TEMPLATE_PATH = os.path.join(DIRNAME, '../template')
        sets = {
            "template_path":TEMPLATE_PATH,
            "static_path":STATIC_PATH,
            "debug":True,
        }
        tornado.web.Application.__init__(self, [
            (r"/test1", TestHandler1),
            (r"/test2", TestHandler2),
        ], **sets)

def main():
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(8888)
    print "Let s start"
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

但是如果我访问test1那么我需要等待调用返回才能访问test2。根据我的理解,我需要使用gen.sleep(5)。但这只是一个例子。假设我没有在bash上运行sleep 5,而是运行ssh somewhere 'do_something',这需要一些时间才能运行。

我被告知“这个功能不是异步的”。所以我的问题是如何使自定义函数异步?

编辑:搜索了一下后,我发现在这里使用了tornado.process https://gist.github.com/FZambia/5756470。但我的子进程来自第三方,所以它不是我能覆盖的东西。所以我的问题也是,如何将第三方库与gen.coroutine系统集成?

解决方案:感谢下面的评论,我得到了一个解决方案:

import tornado.web
import tornado.httpserver
import tornado.gen
import tornado.concurrent
import subprocess
import os

from concurrent import futures

# Create a threadpool, and this can be shared around different python files
# which will not re-create 10 threadpools when we call it.
# we can a handful of executors for running synchronous tasks

# Create a 10 thread threadpool that we can use to call any synchronous/blocking functions
executor = futures.ThreadPoolExecutor(10)

def letswait():
    result_future = tornado.concurrent.Future()
    subprocess.check_output(["sleep", "5"])
    result_future.set_result(42)
    return result_future

class TestHandler1(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        value = yield executor.submit(letswait)
        self.render("test.html", num=value)

class TestHandler2(tornado.web.RequestHandler):
    def get(self):
        self.render("test.html", num=66)

class Application(tornado.web.Application):
    def __init__(self):
        DIRNAME = os.path.dirname(__file__)
        STATIC_PATH = os.path.join(DIRNAME, '../static')
        TEMPLATE_PATH = os.path.join(DIRNAME, '../template')
        sets = {
            "template_path":TEMPLATE_PATH,
            "static_path":STATIC_PATH,
            "debug":True,
        }
        tornado.web.Application.__init__(self, [
            (r"/test1", TestHandler1),
            (r"/test2", TestHandler2),
        ], **sets)

def main():
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(8888)
    print "Let s start"
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

1 个答案:

答案 0 :(得分:4)

我在这里问了一个类似的问题:Python Tornado - Confused how to convert a blocking function into a non-blocking function

问题是您的函数可能受CPU限制,唯一的方法是使用执行程序。

from concurrent import futures

# Create a threadpool, and this can be shared around different python files
# which will not re-create 10 threadpools when we call it.
# we can a handful of executors for running synchronous tasks

# Create a 10 thread threadpool that we can use to call any synchronous/blocking functions
executor = futures.ThreadPoolExecutor(10)

然后你可以这样做:

@gen.coroutine
def get(self):
    json = yield executor.submit(some_long_running_function)

这个任务将被搁置,并且独立运行,因为有一个yield关键字,龙卷风会在它当前运行的程序和你的进程之间进行纯线程切换时做一些其他事情。这似乎对我来说很好。

换句话说,您可以将子进程包装在执行程序中,并且它将被异步处理。

如果您不想使用执行程序,那么您的功能似乎需要以状态机方式实现。

另一篇文章:https://emptysqua.re/blog/motor-internals-how-i-asynchronized-a-synchronous-library/

请注意,Momoko(Postgres)和Motor(MongoDB)都是I / O界限。

编辑: 我不确定你对龙卷风有什么用处。当我做很多I / O时,我使用Tornado,因为我是I / O绑定的。但是,我想如果你的用途更多的是CPU绑定,你可能想看看Flask。您可以轻松使用Gunicorn和Flask来创建简单的东西,并使用多个核心。试图在Tornado中使用多线程或多核可能会让你头疼,因为Tornado中的很多东西都不是线程安全的。

编辑2:删除.result()调用。