V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Tornado Documentation
http://www.v2ex.com/tornado/
Tornado on GitHub
https://github.com/facebook/tornado/
Tornado Gists
http://tornadogists.org/
aoscici2000
V2EX  ›  Tornado

Tornado 的异步 怎么写的

  •  
  •   aoscici2000 · 2019-01-15 01:43:42 +08:00 · 10103 次点击
    这是一个创建于 2141 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看得教程一头雾水, 只能理解那个自带的 http 请求, 看了很久也没看到有比如数据库查询, 文件处理什么的其他例子, 后来去看看其他教程, 实现倒算是勉强实现了, 但感觉也太复杂了吧? ?

    就比如例子中的我假设有个长耗时的任务, 怎么写才会简单点?

    
    import tornado.ioloop
    import tornado.web
    import time
    import _thread
    
    
    # 模拟耗时任务
    def long_work(arg):
        time.sleep(5)
        yield arg * 1024
    
    
    def mycoroutine(func):
        def wrapper(self):
            gen = func(self)
            work_gen = next(gen)
            def fun():
                result = next(work_gen)
                try:
                	gen.send(result)
                except StopIteration:
                	pass
            _thread.start_new_thread(fun, ())
        return wrapper
    
    
    class IndexHandler(tornado.web.RequestHandler):
        @tornado.web.asynchronous
        @mycoroutine
        def get(self):
            arg = 10    # 假设请求的参数
            result = yield long_work(arg)
            self.finish(f'<h1>Index {result}</h1>')
    
    
    class SyncHandler(tornado.web.RequestHandler):
        def get(self):
        	self.write('<h1>SyncHandler</h1>')
    
    
    def make_app():
        return tornado.web.Application([
            (r'/', IndexHandler),
            (r'/sync', SyncHandler)
        ])
    
    
    if __name__ == '__main__':
        app = make_app()
        app.listen(8888)
        tornado.ioloop.IOLoop.current().start()
        
    
    13 条回复    2019-05-27 16:00:35 +08:00
    Vegetable
        1
    Vegetable  
       2019-01-15 03:09:32 +08:00 via iPhone
    拥抱 asyncio 吧…
    asyncio 比 tornado 好理解的多,回头再来看 tornado 也好
    feisan
        2
    feisan  
       2019-01-15 03:09:38 +08:00
    1、尽量使用支持 tornado 异步 io 的库,比如用 tornado_mysql 访问 MySQL。
    2、配合 ThreadPoolExecutor 使用,把同步阻塞任务放到线程池。
    3、把同步阻塞的处理放到一个单独的进程,提供访问接口,然后通过非阻塞的方式去调用它。比如把数据库访问做成独立的 web 服务,然后在 tornado 里用 HTTP 去访问。或者把同步阻塞任务做成 celery 的 task,使用 tornado_celery 取调用。
    janxin
        3
    janxin  
       2019-01-15 10:10:22 +08:00
    time.sleep 是阻塞操作,因此你这里模拟耗时任务时也是阻塞的。最新的 Tornado 也是使用的 asyncio 驱动了,所以要么考虑一下了解 asyncio ?

    显示上下文切换(async/await/@coroutine)之类的,只有在声明处标示切换,如果某函数未切换(无 await)则仍旧是阻塞执行。另外虽然 yield 是在 Tornado 中用于切换,但是不代表用了就是异步行为,这里比较容易混淆。在使用 async/await 语法之后与普通 yield 行为区分开会更清晰。
    zhengxiaowai
        4
    zhengxiaowai  
       2019-01-15 11:08:48 +08:00
    @aoscici2000 https://hexiangyu.me/2017/01/29/real-tornado-async-noblocking/
    发现好多人不知道怎么用 tornado 写异步
    haozi3156666
        5
    haozi3156666  
       2019-01-15 11:31:27 +08:00
    数据库查询用 sqlalchemy 也行的,本身框架不带数据库操作相关模块的
    aoscici2000
        6
    aoscici2000  
    OP
       2019-01-15 12:48:09 +08:00
    @zhengxiaowai 文章倒是大部分看得懂, 但我发现可能大多新手也跟我一样, 迷惑点是耗时操作如何写成不堵的, 教程例子大多都是直接就用 gen.sleep, async.sleep , 关键是这两个东西是怎么实现的(源码也是看得稀里糊涂的)
    aoscici2000
        7
    aoscici2000  
    OP
       2019-01-15 12:53:33 +08:00
    @janxin 看过些, 感觉更难懂哈哈, 主要是很多实例直接就跳过了如何把堵塞的变成不堵的步骤, 我是卡在了这里...比如何如写一个不堵的函数?
    janxin
        8
    janxin  
       2019-01-15 13:43:47 +08:00
    @aoscici2000 看原理先,不要先看代码。asyncio 的代码质量没有特别高,而且很多边缘状况处理,先从源码很容易迷失。

    使用的第一步难道不是先知道我这个是不是异步的么?在 asyncio 中,标记了 coroutine 的都是异步非阻塞的,没标记的都是会阻塞的。先从正确使用开始,换句话说就是没有 await 的地方都有可能阻塞。
    aoscici2000
        9
    aoscici2000  
    OP
       2019-01-15 16:37:58 +08:00
    @janxin 头大就头大在第一步, 到底怎么写才算是非阻塞的, 其实很多教程的实例大致还能理解的, 就是这个 asyncio.sleep(x) 实在想知道它是怎么实现的. 例如下面这段的 work 是怎么样才能实现跟 asyncio.sleep 那样不阻塞得拿到完成的数据

    ```python
    async def work(sec):
    time.sleep(sec)
    return [i*100 for i in range(sec)]


    async def req_a(num):
    res = await work(num)
    print('in req_a:', res)


    async def req_b(num):
    res = await work(num)
    print('in req_b:', res)


    if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    tasks = [req_a(5), req_b(2)]
    loop.run_until_complete(asyncio.wait(tasks))
    print('All finished.')
    loop.close()
    ```
    coroutine
        10
    coroutine  
       2019-01-16 10:45:14 +08:00
    你应该先学习一下
    1. 事件驱动、IO 多路复用 的知识(CSAPP 和 UNP 里有讲)。
    比如先学习一下 select/poll 系统调用,OSX 下的 kqueue, 或者 Linux 下的 epoll(epoll_create, epoll_ctl, epoll_wait)的系统调用知识。
    2. Python 本身的 yield, 和 send 分别实现了函数运行时的挂起和唤醒,丢到双端队列里,配合事件驱动每次去取。

    然后再回头来看 asyncio 里是如何使用事件驱动的。比如你提到的 async.sleep. 实际就是下一次 epoll timeout 时的返回。

    ---

    Tornado 早期自己利用 epoll 写了事件驱动的源码, 前期也有替换 asyncio 的事件循环的代码: http://www.tornadoweb.org/en/stable/asyncio.html 后来的版本,**似乎**和 asyncio 做了兼容。

    另外,从写代码的角度,你可以把直接使用 async await 语法, 而不使用装饰器: http://www.tornadoweb.org/en/stable/guide/coroutines.html#native-vs-decorated-coroutines

    另外,《 Python Cookbook 》里也有使用事件驱动来实现同时 handle 多个请求的例子,都可以参考着学习一下。
    coroutine
        11
    coroutine  
       2019-01-16 10:46:15 +08:00
    asyncio.sleep 不是 async.sleep,打错
    coroutine
        12
    coroutine  
       2019-01-21 14:42:42 +08:00
    你如果确实有同步的库需要在异步环境执行,可以参考 https://stackoverflow.com/questions/22190403/how-could-i-use-requests-in-asyncio
    itwhat
        13
    itwhat  
       2019-05-27 16:00:35 +08:00
    py3 用 async 加 await,py2 就用协程装饰器(@gen.coroutine)加 yield
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3047 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 14:18 · PVG 22:18 · LAX 06:18 · JFK 09:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.