最近想在 handler 中些自己的异步程序,原本以为只要通过 coroutine 装饰以后返回个 future 对象就可以让程序实现异步,但尴尬的是测试后发现然!并!卵!
所以兴致勃勃的去看了 tornado 源码,在看到 ioloop 将 socket 注册事件以及回调函数后就懵逼了,因为,虽然这样是注册了,但是之后代码里面似乎并没体现出 tornado 如何在注册的事件发生之后怎么回调回调函数。后来想了下, ioloop 的的底层是 Linux 的 epoll ,这样一来, tornado 是不是把调用回调函数的任务交给了操作系统?而我们只用关心向操作系统指明回调函数以及调用回调函数的事件就行了????
以上是我的理解,如果不对请大神指出~~~~
1
SlipStupig 2016-03-06 20:56:31 +08:00
说一下个人理解, epoll 作用是用来管理 socket 句柄的,这个哪怕你用 c++去实现 epoll 也是这样, epoll 一共有以下几个状态: EPOLLIN 、 EPOLLOUT 、 EPOLLHUP 。 tornado 是根据系统返回的状态,来进行操作而不是系统来操作 tornado ,具体实现见 epoll server 代码
https://github.com/derwiki/epoll/blob/master/server.py |
2
Stargi 2016-03-06 21:21:47 +08:00 3
在 ioloop.start()方法那里可以看到 ioloop 触发回调有两个方面,一个是用 poll 监听 socket 的事件然后触发 socket 对应的回调,另外 ioloop 还维护一个_callbacks 的列表,每一轮 poll 之前都会逐个调用_callbacks 里面的回调函数。另外 future 对象做的其实就是保存回调函数,当异步任务完成时将回调函数加入_callbacks 里面,等下一轮 poll 之前调用。
个人的之前做的一点笔记 http://sineyuan.github.io/2016/01/19/tornado-source-code-2/,有错误欢迎指正 |
3
mulog 2016-03-06 22:52:37 +08:00
tornado 的回调函数是自己管理的, poll 了之后根据每一个 fd 找到对应的 callback 然后把事件交给它处理。
|
4
decaywood 2016-03-06 22:56:18 +08:00
https://github.com/decaywood/GithubSpray 这是我 tornado 异步爬虫的一个例子,简单易懂, clone 下来看吧 官方文档我也翻译了一份 http://blog.decaywood.me/2016/01/14/tornado/
|
5
triThirty OP 感谢大家~~~都是干货~~~
|
6
calease 2016-03-07 02:27:31 +08:00
你的 yield 对象必须是 async 的才能使 handler async 。
所以你的 yield 对象必须也是一个 coroutine , 然后这个 coroutine 必须也 yield 它呼叫的 coroutine , 依次下去最终 yield 一个实现了 coroutine 的 API , 比如 tornado.gen.sleep |
7
binux 2016-03-07 04:29:45 +08:00
tornado 干了一个 dispatcher 的活,将事件和回调串了起来。
|
8
triThirty OP @decaywood 我看了你的代码,在调用的底层,其实还是使用了 httpclient.AsyncHTTPClient().fetch(url)。但现在我想实现自己的业务逻辑异步,不让在执行自己业务逻辑时阻塞其他的请求,而不是用 tornado 封装好的的 AsyncHTTPClient ,我尝试了几种方法,似乎都不能达到目的。请大神指教~~~
|
10
triThirty OP |
11
triThirty OP @tornado.gen.coroutine
def get(self): print 'start' future = Future() def callbacl(self): print('sleeping') time.sleep(5) future.set_result('2333') tornado.ioloop.IOLoop.instance().add_callback(callbacl,self) yield future print('end') 我写了这么一段测 demo ,发现程序在执行 future.set_result 之后就可以就收新的请求,但是在执行 time.sleep(5)时, get 方法是阻塞的,不能接受新请求。如何让 get 方法称为非阻塞的? |
12
decaywood 2016-03-07 10:23:32 +08:00
@triThirty 我觉得你首先要搞明白你业务逻辑是什么阻塞了程序执行,如果是计算耗时,那弄成异步有什么意义呢?异步的本质是提高单线程 CPU 效率,降低 IO 造成的性能瓶颈。如果你任然是用 http 调用别人接口,那跟我的 demo 没有本质区别啊。
|
13
zeayes 2016-03-07 10:24:08 +08:00
time.sleep 把进程给阻塞了,可以用 IOLoop.add_timeout 代替
|
14
swjtutipo 2016-03-07 10:37:14 +08:00
time.sleep(5)是阻塞的啊 你需要换成 tornado 的非阻塞 sleep
|
16
jmp2x 2016-03-07 11:01:33 +08:00
关于 tornado 中你可以用两种方式进行异步,一种是 callback ,一种是 yield(实质也是 callback,只不过这个 callback 是 send(result)), http://jmpews.github.io/posts/async-coroutine-callback.html 你需要参考源码去阅读这篇文章。
|
17
triThirty OP 是这样的,我现在做微信后台,前端用户通过微信向后台发送请求,但是请求内容是 xml 的,所以后台需要对 xml 进行解析,并产生响应返回给用户。之前的代码在解析 xml 过程中就阻塞了,导致一个请求处理完,才能处理另一个请求。
|
18
decaywood 2016-03-07 11:33:11 +08:00 1
@triThirty 你这种需求异步也没意义,就像你饭馆就一个厨师,你揽那么多客有用吗?解决办法就是开多个 tornado 进程,用 nginx 进行反向代理
|
20
triThirty OP 经过几番打断带你查看源码执行,终于知道了:
在 ioloop.py 文件中的 PollIOLoop 类中的 start 方法中有段 for callback in callbacks: self._run_callback(callback) #执行耗时任务会被阻塞掉 for timeout in due_timeouts: #执行对应的超时回调 if timeout.callback is not None: 如注解所示, ioloop 对象会循环单线程的执行 callbacks 中的 callback 函数,由于 tornado 内部是单线程的,所以 self._run_callback(callback)执行任何耗时任务都会阻塞当前线程。 以上是我看源码的理解,如果理解不对希望批评指正~~~~ |
21
zhicheng 2016-03-08 11:35:13 +08:00
epoll 是一个机制,不是黑魔法,并不会让你的计算 “消失” , cpu bound 的应用解决办法是你多开几个进程(缓解)或 使用队列 (解决)比如 celery 。
不过我怀疑是你的代码写得有问题, XML 解析能有多慢。 |
22
triThirty OP @zhicheng 谢谢,了解~~~我说的比喻的不准确,给后来看贴的小伙伴说明下,我这里说的解析 xml 是想比喻下耗时任务。
|
23
triThirty OP http://zqdevres.qiniucdn.com/data/20100927213110/index.html 这是一个 lunix 下网络编程的极好的文章,给有需要的小伙伴。
|