tornado 菜鸟看源码关于 epoll 的问题

2016-03-06 19:52:03 +08:00
 triThirty

最近想在 handler 中些自己的异步程序,原本以为只要通过 coroutine 装饰以后返回个 future 对象就可以让程序实现异步,但尴尬的是测试后发现然!并!卵!
所以兴致勃勃的去看了 tornado 源码,在看到 ioloop 将 socket 注册事件以及回调函数后就懵逼了,因为,虽然这样是注册了,但是之后代码里面似乎并没体现出 tornado 如何在注册的事件发生之后怎么回调回调函数。后来想了下, ioloop 的的底层是 Linux 的 epoll ,这样一来, tornado 是不是把调用回调函数的任务交给了操作系统?而我们只用关心向操作系统指明回调函数以及调用回调函数的事件就行了????
以上是我的理解,如果不对请大神指出~~~~

5708 次点击
所在节点    Python
23 条回复
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
Stargi
2016-03-06 21:21:47 +08:00
在 ioloop.start()方法那里可以看到 ioloop 触发回调有两个方面,一个是用 poll 监听 socket 的事件然后触发 socket 对应的回调,另外 ioloop 还维护一个_callbacks 的列表,每一轮 poll 之前都会逐个调用_callbacks 里面的回调函数。另外 future 对象做的其实就是保存回调函数,当异步任务完成时将回调函数加入_callbacks 里面,等下一轮 poll 之前调用。
个人的之前做的一点笔记 http://sineyuan.github.io/2016/01/19/tornado-source-code-2/,有错误欢迎指正
mulog
2016-03-06 22:52:37 +08:00
tornado 的回调函数是自己管理的, poll 了之后根据每一个 fd 找到对应的 callback 然后把事件交给它处理。
decaywood
2016-03-06 22:56:18 +08:00
https://github.com/decaywood/GithubSpray 这是我 tornado 异步爬虫的一个例子,简单易懂, clone 下来看吧 官方文档我也翻译了一份 http://blog.decaywood.me/2016/01/14/tornado/
triThirty
2016-03-07 00:14:51 +08:00
感谢大家~~~都是干货~~~
calease
2016-03-07 02:27:31 +08:00
你的 yield 对象必须是 async 的才能使 handler async 。
所以你的 yield 对象必须也是一个 coroutine ,
然后这个 coroutine 必须也 yield 它呼叫的 coroutine ,
依次下去最终 yield 一个实现了 coroutine 的 API ,
比如 tornado.gen.sleep
binux
2016-03-07 04:29:45 +08:00
tornado 干了一个 dispatcher 的活,将事件和回调串了起来。
triThirty
2016-03-07 09:55:21 +08:00
@decaywood 我看了你的代码,在调用的底层,其实还是使用了 httpclient.AsyncHTTPClient().fetch(url)。但现在我想实现自己的业务逻辑异步,不让在执行自己业务逻辑时阻塞其他的请求,而不是用 tornado 封装好的的 AsyncHTTPClient ,我尝试了几种方法,似乎都不能达到目的。请大神指教~~~
zeayes
2016-03-07 09:59:35 +08:00
@triThirty 业务逻辑不是 HTTP 协议?
triThirty
2016-03-07 10:07:27 +08:00
@zeayes 是 http 协议,相当于我要在 @decaywood 大神调用 httpclient.AsyncHTTPClient().fetch(url)的地方调用一个别人的三方库,这个三方库是用的 http 协议。
triThirty
2016-03-07 10:12:01 +08:00
@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 方法称为非阻塞的?
decaywood
2016-03-07 10:23:32 +08:00
@triThirty 我觉得你首先要搞明白你业务逻辑是什么阻塞了程序执行,如果是计算耗时,那弄成异步有什么意义呢?异步的本质是提高单线程 CPU 效率,降低 IO 造成的性能瓶颈。如果你任然是用 http 调用别人接口,那跟我的 demo 没有本质区别啊。
zeayes
2016-03-07 10:24:08 +08:00
time.sleep 把进程给阻塞了,可以用 IOLoop.add_timeout 代替
swjtutipo
2016-03-07 10:37:14 +08:00
time.sleep(5)是阻塞的啊 你需要换成 tornado 的非阻塞 sleep
zeayes
2016-03-07 10:52:38 +08:00
@triThirty tornado.gen.sleep(5)
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 你需要参考源码去阅读这篇文章。
triThirty
2016-03-07 11:11:29 +08:00
是这样的,我现在做微信后台,前端用户通过微信向后台发送请求,但是请求内容是 xml 的,所以后台需要对 xml 进行解析,并产生响应返回给用户。之前的代码在解析 xml 过程中就阻塞了,导致一个请求处理完,才能处理另一个请求。
decaywood
2016-03-07 11:33:11 +08:00
@triThirty 你这种需求异步也没意义,就像你饭馆就一个厨师,你揽那么多客有用吗?解决办法就是开多个 tornado 进程,用 nginx 进行反向代理
hitsmaxft
2016-03-07 13:08:40 +08:00
@triThirty 纯 cpu 消耗异步没意义,只能靠多核分担工作量
triThirty
2016-03-08 11:20:04 +08:00
经过几番打断带你查看源码执行,终于知道了:
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)执行任何耗时任务都会阻塞当前线程。
以上是我看源码的理解,如果理解不对希望批评指正~~~~

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

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

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

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

© 2021 V2EX