Tornado 的异步 怎么写的

2019-01-15 01:43:42 +08:00
 aoscici2000

看得教程一头雾水, 只能理解那个自带的 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()
    
10100 次点击
所在节点    Tornado
13 条回复
Vegetable
2019-01-15 03:09:32 +08:00
拥抱 asyncio 吧…
asyncio 比 tornado 好理解的多,回头再来看 tornado 也好
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
2019-01-15 10:10:22 +08:00
time.sleep 是阻塞操作,因此你这里模拟耗时任务时也是阻塞的。最新的 Tornado 也是使用的 asyncio 驱动了,所以要么考虑一下了解 asyncio ?

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

使用的第一步难道不是先知道我这个是不是异步的么?在 asyncio 中,标记了 coroutine 的都是异步非阻塞的,没标记的都是会阻塞的。先从正确使用开始,换句话说就是没有 await 的地方都有可能阻塞。
aoscici2000
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
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
2019-01-16 10:46:15 +08:00
asyncio.sleep 不是 async.sleep,打错
coroutine
2019-01-21 14:42:42 +08:00
你如果确实有同步的库需要在异步环境执行,可以参考 https://stackoverflow.com/questions/22190403/how-could-i-use-requests-in-asyncio
itwhat
2019-05-27 16:00:35 +08:00
py3 用 async 加 await,py2 就用协程装饰器(@gen.coroutine)加 yield

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/527071

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX