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
fy
V2EX  ›  Python

tornado4 的异步究竟怎么玩?

  •  
  •   fy ·
    fy0 · 2015-07-15 00:03:04 +08:00 · 3739 次点击
    这是一个创建于 3218 天前的主题,其中的信息可能已经有所发展或是发生改变。

    tornado 4.x 之后,官方搞了自己的协程,弄了一个Future之类的。

    以前用他的异步一直也就是弄弄AsyncHTTPClient什么的,甚是浅显。

    但是作为一个新时代的四有青年,生在红旗下,长在春风里,怎么能满足于此呢对不对。


    于是我定了一个这样的需求:

    tornado接受请求 -> 后端消耗若干时间执行任务

    要求是接受请求是异步的


    查了一些资料写了这样的代码:

    main.py

    # coding:utf-8
    
    import tornado.ioloop
    import tornado.web
    from tornado.gen import coroutine
    from tornado.web import asynchronous
    import tasync
    
    
    class MainHandler(tornado.web.RequestHandler):
        @coroutine
        #@asynchronous
        def get(self):
            print('Hi')
            result = yield tasync.async()
            self.write("%s" % result )
    
    class MainHandler2(tornado.web.RequestHandler):
        def get(self):
            self.write('test')
    
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/a", MainHandler2),
    ])
    
    if __name__ == "__main__":
        application.listen(8888)
        print('Started.')
        tasync.init()
        tornado.ioloop.IOLoop.instance().start()
    

    tasync.py

    # coding:utf-8
    
    import time
    import threading
    from functools import partial
    from tornado.ioloop import IOLoop
    from tornado.concurrent import Future
    
    count = 1
    future_dict = {}
    
    def async(*args, **kwargs):
        global count
        future = Future()
        callback = kwargs.pop("callback", None)
        if callback:
            IOLoop.instance().add_future(future, lambda future: callback(future.result()))
    
        future_dict[count] = [future, time.time()]
        count += 1
    
        return future
    
    
    def init():
        interval = 0.3
        io_loop = IOLoop.instance()
    
        def the_loop():
            while True:
                t = time.time()
                for k, v in future_dict.items():
                    if t - v[1] >= 5:
                        io_loop.add_callback(_on_result, k, v[0])
                        del future_dict[k]
                time.sleep(interval)
    
        threading.Thread(target=the_loop, args=()).start()
    
    
    def _on_result(result, future):
        print(result, time.time())
        future.set_result(result)
    

    上面俩文件复制下来就可以跑,py2/3皆可。


    这个代码做了这样一件事情:

    我的代码有两个url,/ 是我预设的异步点, /a 是一个调试页面。

    访问/ 的话,程序应该在5秒钟后返回。

    程序分为两个线程:tornado主线程,任务轮询线程(完成任务后给ioloop发消息)


    但问题是这样的:

    访问/,立即访问/a : /页面在等待状态,/a页面响应,正常。
    访问/,立即再开另一页面访问/,第二张页面居然被阻塞了!

    你们看一下日志就明白:

    Started.
    Hi
    1436887952.72
    Hi
    1436887958.91

    Hi是 / 接受请求,输出时间是回调完成。


    我表示非常不解,求解惑。

    PS: 我知道单以这个需求而论,tornado-celery是可用的,但是tcelery满足不了我其他的需求,所以不考虑。另外就主要是知其然知其所以然了。

    14 条回复    2015-08-05 10:11:32 +08:00
    wy315700
        1
    wy315700  
       2015-07-15 00:07:21 +08:00
    time.sleep
    是阻塞的,,所以 被阻塞了。

    tornado的异步要求很高的,,,
    fy
        2
    fy  
    OP
       2015-07-15 00:11:17 +08:00
    @wy315700 sleep在另一个线程里呀,没关系的
    alphonsez
        3
    alphonsez  
       2015-07-15 00:57:41 +08:00
    result = yield tasync.async()
    这个变成coroutine了吧,所以你的print是在async task执行完成才执行的。
    alphonsez
        4
    alphonsez  
       2015-07-15 01:01:57 +08:00
    你要直接async,应该是这样的:

    @asynchronous
    def get(self):
    print('Hi')
    result = tasync.async()
    self.write("%s" % result )
    return result

    不过这个result是一个future. 如果你要print那个真正的result,肯定得等5秒过后啊。本来的coroutine写法相当于js里的(宽恕我不怎么懂python吧……):

    def get(self):
    print('Hi')
    return tasync.async().then(function(result) { self.write("%s" % result ); return result; });
    fy
        5
    fy  
    OP
       2015-07-15 01:35:44 +08:00
    @alphonsez

    我不太明白的是为什么get请求被堵住了呢,这很不科学呀!难道一个请求只给一个协程,所以只能排队?好像也说不通啊
    wy315700
        6
    wy315700  
       2015-07-15 07:44:01 +08:00
    @fy
    time.sleep
    也会阻塞线程的。。
    zeayes
        7
    zeayes  
       2015-07-15 10:02:27 +08:00
    tornado的异步是针对网络io阻塞情况下的异步。
    janxin
        8
    janxin  
       2015-07-15 12:15:25 +08:00
    如果你需要单个的等待,可以使用gen.sleep来解决你的需求
    fy
        9
    fy  
    OP
       2015-07-15 12:36:38 +08:00
    @wy315700 大哥,这个就是故意用来阻塞调度线程的,不然while True直接轮询吗?


    @zeayes 还是不太明白,网络io阻塞,那不就应该是有能力接收多个请求,然后让他们等着,等有结果在返回给他们吗?可是我这里请求1和请求2互相不阻塞,但是两个请求1阻塞了,为啥呢
    mulog
        10
    mulog  
       2015-07-15 12:48:42 +08:00   ❤️ 1
    哦呵呵呵 tornado 躺着中枪
    你用的是浏览器吧?你试试用两个不同的浏览器访问,或者直接开两个 terminal 用 curl 访问
    毫无问题
    fy
        11
    fy  
    OP
       2015-07-15 12:53:38 +08:00
    @mulog

    哦草日了头像了……原来是浏览器的锅。万万没想到啊!那么事实上我的代码已经做好异步了……

    Started.
    Hi
    Hi
    (1, 1436935932.168)
    (2, 1436935933.073)
    alphonsez
        12
    alphonsez  
       2015-07-15 16:08:01 +08:00
    yield不就是等结果吗?没有堵住啊。你的thread应该是闲着的啊
    alphonsez
        13
    alphonsez  
       2015-07-15 16:12:23 +08:00
    @alphonsez 好吧 看错日志了。
    7harryprince
        14
    7harryprince  
       2015-08-05 10:11:32 +08:00
    这么写简便多了,赞
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4017 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 05:14 · PVG 13:14 · LAX 22:14 · JFK 01:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.