Tornado请求响应时间太长,即使使用gen.coroutine

时间:2016-12-26 04:37:20

标签: python asynchronous tornado

我是异步编程的新手并且被搞乱了。

当我使用gen.coroutine装饰RequestHandler但发现请求仍被阻止时,我感到困惑。

这是一个简短的代码,包含python 2.7.11和tornado 4.4.1

@gen.coroutine
def store_data(data):
    try:
       # parse_data
       ...
    except ParseError as e:
       logger.warning(e)
       return
    yield motor.insert_many(parsed_data)  # asynchronous mongo
    print motor.count()

class MainHandler(RequestHandler):
    @gen.coroutine
    def post(self):
        try:
            some_argument = int(self.get_argument("some", 0))
            data = self.request.body
        except Exception:
            self.write("Improper Argument")
            self.finish()
            return
        IOLoop.current().spawn_callback(lambda: store_data(data))
        self.write("Request Done")
        self.finish()

我用10个线程进行了测试。根据访问日志中的响应时间,我想一些请求被阻止了

[I 161222 15:40:22 web:1971] 200 POST /upload/ (::1) 9.00ms
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 8.00ms
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 8.00ms
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 7.00ms
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 8.00ms
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 9.00ms
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 8.00ms
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 9.00ms
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 701.00ms # Seem blocked
[I 161222 15:40:23 web:1971] 200 POST /upload/ (::1) 696.00ms # Seem blocked

更新

set_blocking_log_threshold(0.5)

的追溯讯息
File "********", line 74, in <dictcomp>
    data = [dict({"sid": sid}, **{key: value for key, value in i.iteritems()

此代码的整行是

data = [dict({"sid": sid}, **{key: value for key, value in i.iteritems() if key in need_cols}) for i in v_data] 

解压缩的逻辑就是这样的

data = []
# `v_data` is a huge dict which could be considered as a mongo collection, and `i` as a mongo document
for i in v_data:  
    temp = {key: value for key, value in i.iteritems() if key in need_cols}  # discard some keys
    temp["sid"] = sid  # add same `sid` to all items
    data.append(temp)

我把它改成了发电机

def data_generator(v_data, need_cols, sid):
    for i in v_data:  
        temp = {key: value for key, value in i.iteritems() if key in need_cols}  # discard some keys
        temp["sid"] = sid  # add same `sid` to all items
        yield temp

@gen.coroutine
def store_data(data):
    try:
       # parse_data
       ...
    except ParseError as e:
       logger.warning(e)
       return
    ge = data_generator(v_data, need_cols, sid)
    yield motor.insert_many(ge)  # asynchronous mongo
    print motor.count()

没有报告任何阈值警告日志,但响应时间似乎仍然被阻止

[I 170109 17:26:32 web:1971] 200 POST /upload/ (::1) 3.00ms
[I 170109 17:26:33 web:1971] 200 POST /upload/ (::1) 2.00ms
[I 170109 17:26:33 web:1971] 200 POST /upload/ (::1) 4.00ms
[I 170109 17:26:33 web:1971] 200 POST /upload/ (::1) 3.00ms
[I 170109 17:26:33 web:1971] 200 POST /upload/ (::1) 3.00ms
[I 170109 17:26:33 web:1971] 200 POST /upload/ (::1) 2.00ms
[I 170109 17:26:33 web:1971] 200 POST /upload/ (::1) 354.00ms
[I 170109 17:26:33 web:1971] 200 POST /upload/ (::1) 443.00ms

然后我将阈值设置为0.2秒。得到了这条消息

  File "*******", line 76, in store_data
    increment = json.load(fr)
  File "/usr/local/python2.7/lib/python2.7/json/__init__.py", line 291, in load
    **kw)
  File "/usr/local/python2.7/lib/python2.7/json/__init__.py", line 339, in loads
    return _default_decoder.decode(s)
  File "/usr/local/python2.7/lib/python2.7/json/decoder.py", line 364, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/python2.7/lib/python2.7/json/decoder.py", line 380, in raw_decode
    obj, end = self.scan_once(s, idx)

现在我不知道如何使这个语句异步

2 个答案:

答案 0 :(得分:0)

我认为问题可能在于您如何调用store_data协程功能。你必须以正确的方式调用协同程序。正如提到here

  

几乎在所有情况下,任何调用协程的函数都必须是a   协程本身,并在通话中使用yield关键字。

因此必须像store_data而不是yield store_data()一样调用store_data()。在这里

IOLoop.current().spawn_callback(lambda: store_data(data))

我猜您使用的是lambda因为您希望将data作为参数提供给您的函数,但您可以使用spawn_callback本身执行此操作。你可以试试这个:

IOLoop.current().spawn_callback(store_data, data)

希望这会有所帮助。

答案 1 :(得分:0)

如果该函数永远不会@gen.coroutine,则使用yields修饰函数无效。

您的post()方法看起来是正确的:它永远不会阻止或做任何干扰IOLoop的事情。但是,IOLoop是共享资源,阻止它的任何内容都可能导致您看到的时间。我怀疑你在store_data(或程序中的其他地方)没有向我们展示的东西是阻塞的。要突出显示此阻止的位置,请在程序开头调用IOLoop.current().set_blocking_log_threshold(0.5),并在IOLoop被阻止半秒后记录堆栈跟踪。