tornado4 的异步究竟怎么玩?

2015-07-15 00:03:04 +08:00
 fy

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满足不了我其他的需求,所以不考虑。另外就主要是知其然知其所以然了。

3855 次点击
所在节点    Python
14 条回复
wy315700
2015-07-15 00:07:21 +08:00
time.sleep
是阻塞的,,所以 被阻塞了。

tornado的异步要求很高的,,,
fy
2015-07-15 00:11:17 +08:00
@wy315700 sleep在另一个线程里呀,没关系的
alphonsez
2015-07-15 00:57:41 +08:00
result = yield tasync.async()
这个变成coroutine了吧,所以你的print是在async task执行完成才执行的。
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
2015-07-15 01:35:44 +08:00
@alphonsez

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


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

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

Started.
Hi
Hi
(1, 1436935932.168)
(2, 1436935933.073)
alphonsez
2015-07-15 16:08:01 +08:00
yield不就是等结果吗?没有堵住啊。你的thread应该是闲着的啊
alphonsez
2015-07-15 16:12:23 +08:00
@alphonsez 好吧 看错日志了。
7harryprince
2015-08-05 10:11:32 +08:00
这么写简便多了,赞

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

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

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

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

© 2021 V2EX