AsyncHTTPClient 阻止我的 Tornado IOLoop

AsyncHTTPClient blocking my Tornado IOLoop

你好吗?

这几天一直在纠结,好像没能完全理解tornado gen库。

我有这段代码,例如:

@gen.coroutine
def get(self, build_id=None):
    status_query = self.get_query_arguments("status")
    limit_query = self.get_query_arguments("limit")

    results = [self._dummy() for i in range(15)]
    yield results

def _dummy(self):
    http_client = tornado.httpclient.AsyncHTTPClient()
    return http_client.fetch("https://www.google.com", headers=self.headers, validate_cert=False)

正如我所想,我的 15 个请求 google 应该几乎同时触发。 "results"列表,应该是futures列表,然后,yield list应该等全部完成。

这确实发生了,但是发出这些请求大约需要 6 秒,并且随着我增加 for 循环的范围,它会逐渐增加。

他们不应该花大约相同的时间准备好吗?

我是不是漏掉了什么?

非常感谢!

AsyncHTTPClient默认max_clients是10个。当你发起15个请求时,其中10个立即开始,但剩下的5个必须等待其他请求完成才能开始。要开始更多并发请求,请将 max_clients 提高到一个更大的数字。 See Tornado's documentation for details on configuring AsyncHTTPClient.

If your requests aren't IO bound then you won't see much change. -

在编程中,这些是我们的主要限制:

  • CPU(每秒可发生的计算次数)
  • 处理器中的缓存访问
  • RAM 访问
  • 磁盘访问
  • 网络访问

在 Python 中,由于 GIL,我们在 CPU 访问方面受到进一步限制。 现代 计算机趋向于多核 - 2、4、8 或 16 - 我们进一步瘫痪1 因为 通常 这些处理器中的每一个都会慢一点。有关 GIL 的更多信息,请查看 David Beazley's GIL talk and Larry Hasting's GIL-ectomy.

为了绕过全局解释器锁,已经开发了几个回调式模块,如 Twisted、Tornado 和 asyncio。这些工作的方式是通过执行一些操作,当它们到达 IO 停止的点时,通常会放弃控制。

例如,如果我正在将数据写入旋转磁盘,也许我可以将 100kb 写入磁盘,但是在等待所有这些信息被写入时,也许我可以离开并做1000次计算,所有数据写入完成。

或者,也许我可以每秒向 Web 服务发出 100 个请求,但我只需要 0.0001 秒来为每个请求执行计算。如果你看一下我花时间的图表,它看起来像这样:

    #            
    #            
    #            
    #            
    #            
    #            
    #            
    #           #
--------------------------
 reading    processing

这些进程允许你做的是交错处理和 reading/writing,通过发送请求数据包,然后做其他事情,然后在某个时候回来读取返回的数据包。

像这样受 IO 约束,您可以看到相当大的加速,因为而不是看起来像这样:

 start    end start     end
--|--------|----|--------|------
 t=0      t=5  t=6      t=11

你可以得到这样的东西:

     start      end
 start|     end  |
--|---|------|---|-
 t=0 t=1    t=5  t=6

但是 如果您的进程受到 CPU 限制,您不会看到任何加速(或至少不会太多),因为您正在花费30s 做处理,只有 1s 做任何类型的等待网络。

在尝试异步方法之前,给出标准的单线程方法并查看 1) 它是否足够快以及 2) 它是否在 network/IO 边界处很慢。

您可以轻松地将 line profiler 用于 Python,并且(如果还没有)将您的读取、处理和写入功能分开,看看您将时间花在哪里了。如果您将大部分时间花在读取函数上,那么是的,您应该会从异步方法中看到相当合理的加速。否则,异步只会减慢您的速度。

1Honestly it's not really that bad, unless you have something super speed critical. And then you should be using the cffi or something to take the speed critical sections and dump them into C. You did figure out which sections are the hold-up, right?