【问题标题】:AsyncHTTPClient blocking my Tornado IOLoopAsyncHTTPClient 阻止了我的 Tornado IOLoop
【发布时间】:2016-11-17 16:35:21
【问题描述】:

你好吗?

最近几天我遇到了这个麻烦,我似乎无法完全理解 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 的请求应该几乎同时触发。 “结果”列表应该是一个期货列表,然后,生成列表应该等待所有这些都完成。

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

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

我错过了什么吗?

非常感谢!

【问题讨论】:

  • 如果您的请求不受 IO 限制,那么您不会看到太大变化。
  • 你能再解释一下吗? :)

标签: python tornado coroutine


【解决方案1】:

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

【讨论】:

  • 但是,在我的示例中,它将创建不同的 AsyncHTTPClient 实例,每个实例只发出一个请求......我错了吗?
  • 是的 - 在内部,AsyncHTTPClient 对象共享一个请求队列。
  • 是的,我刚刚在文档中发现了这一点。那太棒了。 Buuuut,我刚刚将 max_clients 增加到 50,但仍然有同样的问题。随着我添加更多请求,时间仍在增加。 (不超过 50 个限制)
  • 这是正确答案。我必须安装 pycurl 并使用此设置: tornado.httpclient.AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient") 然后一切都按预期工作。
【解决方案2】:

如果您的请求不受 IO 限制,那么您不会看到太多变化。 - Me

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

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

在 Python 中,由于 GIL,我们在 CPU 访问方面受到了进一步的限制。对于倾向于多核(2、4、8 或 16)的现代计算机,我们会被进一步削弱1,因为通常每个这些处理器会慢一些。有关 GIL 的更多信息,请查看 David Beazley's GIL talkLarry Hasting's GIL-ectomy

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

例如,如果我正在向旋转磁盘写入数据,也许我可以将 100kb 写入磁盘,但是当我等待所有这些信息被写入时,也许我可以开始进行 1,000 次计算在所有数据完成写入之前。

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

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

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

像这样受 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 限制,您将不会看到任何加速(或至少不会太多),因为您花费 30 秒进行处理而只花费 1 秒进行任何类型的处理等待网络。

在尝试异步方法之前,先给出标准的单线程方法,看看 1) 是否足够快,以及 2) 在网络/IO 边界上是否慢。

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

1老实说,它并没有那么糟糕,除非你有一些超级速度关键的东西。然后你应该使用cffi 或其他东西来获取速度关键部分并将它们转储到 C 中。你确实找出了哪些部分是阻塞的,对吧?

【讨论】:

    猜你喜欢
    • 2018-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多