V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
triThirty
V2EX  ›  Python

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

  •  
  •   triThirty · 2016-03-06 19:52:03 +08:00 · 5440 次点击
    这是一个创建于 2944 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    23 条回复    2016-03-09 11:24:27 +08:00
    SlipStupig
        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
    Stargi
        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/,有错误欢迎指正
    mulog
        3
    mulog  
       2016-03-06 22:52:37 +08:00
    tornado 的回调函数是自己管理的, poll 了之后根据每一个 fd 找到对应的 callback 然后把事件交给它处理。
    decaywood
        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/
    triThirty
        5
    triThirty  
    OP
       2016-03-07 00:14:51 +08:00
    感谢大家~~~都是干货~~~
    calease
        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
    binux
        7
    binux  
       2016-03-07 04:29:45 +08:00
    tornado 干了一个 dispatcher 的活,将事件和回调串了起来。
    triThirty
        8
    triThirty  
    OP
       2016-03-07 09:55:21 +08:00
    @decaywood 我看了你的代码,在调用的底层,其实还是使用了 httpclient.AsyncHTTPClient().fetch(url)。但现在我想实现自己的业务逻辑异步,不让在执行自己业务逻辑时阻塞其他的请求,而不是用 tornado 封装好的的 AsyncHTTPClient ,我尝试了几种方法,似乎都不能达到目的。请大神指教~~~
    zeayes
        9
    zeayes  
       2016-03-07 09:59:35 +08:00
    @triThirty 业务逻辑不是 HTTP 协议?
    triThirty
        10
    triThirty  
    OP
       2016-03-07 10:07:27 +08:00
    @zeayes 是 http 协议,相当于我要在 @decaywood 大神调用 httpclient.AsyncHTTPClient().fetch(url)的地方调用一个别人的三方库,这个三方库是用的 http 协议。
    triThirty
        11
    triThirty  
    OP
       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
        12
    decaywood  
       2016-03-07 10:23:32 +08:00
    @triThirty 我觉得你首先要搞明白你业务逻辑是什么阻塞了程序执行,如果是计算耗时,那弄成异步有什么意义呢?异步的本质是提高单线程 CPU 效率,降低 IO 造成的性能瓶颈。如果你任然是用 http 调用别人接口,那跟我的 demo 没有本质区别啊。
    zeayes
        13
    zeayes  
       2016-03-07 10:24:08 +08:00
    time.sleep 把进程给阻塞了,可以用 IOLoop.add_timeout 代替
    swjtutipo
        14
    swjtutipo  
       2016-03-07 10:37:14 +08:00
    time.sleep(5)是阻塞的啊 你需要换成 tornado 的非阻塞 sleep
    zeayes
        15
    zeayes  
       2016-03-07 10:52:38 +08:00
    @triThirty tornado.gen.sleep(5)
    jmp2x
        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 你需要参考源码去阅读这篇文章。
    triThirty
        17
    triThirty  
    OP
       2016-03-07 11:11:29 +08:00
    是这样的,我现在做微信后台,前端用户通过微信向后台发送请求,但是请求内容是 xml 的,所以后台需要对 xml 进行解析,并产生响应返回给用户。之前的代码在解析 xml 过程中就阻塞了,导致一个请求处理完,才能处理另一个请求。
    decaywood
        18
    decaywood  
       2016-03-07 11:33:11 +08:00   ❤️ 1
    @triThirty 你这种需求异步也没意义,就像你饭馆就一个厨师,你揽那么多客有用吗?解决办法就是开多个 tornado 进程,用 nginx 进行反向代理
    hitsmaxft
        19
    hitsmaxft  
       2016-03-07 13:08:40 +08:00 via iPhone
    @triThirty 纯 cpu 消耗异步没意义,只能靠多核分担工作量
    triThirty
        20
    triThirty  
    OP
       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)执行任何耗时任务都会阻塞当前线程。
    以上是我看源码的理解,如果理解不对希望批评指正~~~~
    zhicheng
        21
    zhicheng  
       2016-03-08 11:35:13 +08:00
    epoll 是一个机制,不是黑魔法,并不会让你的计算 “消失” , cpu bound 的应用解决办法是你多开几个进程(缓解)或 使用队列 (解决)比如 celery 。

    不过我怀疑是你的代码写得有问题, XML 解析能有多慢。
    triThirty
        22
    triThirty  
    OP
       2016-03-08 11:38:16 +08:00
    @zhicheng 谢谢,了解~~~我说的比喻的不准确,给后来看贴的小伙伴说明下,我这里说的解析 xml 是想比喻下耗时任务。
    triThirty
        23
    triThirty  
    OP
       2016-03-09 11:24:27 +08:00
    http://zqdevres.qiniucdn.com/data/20100927213110/index.html 这是一个 lunix 下网络编程的极好的文章,给有需要的小伙伴。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3324 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 13:29 · PVG 21:29 · LAX 06:29 · JFK 09:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.