在Python 2.7中如何在没有第3方软件包的情况下实现异步请求

时间:2018-08-09 06:30:06

标签: python asynchronous python-requests

我想要的是标题。背景是我有成千上万的请求要发送到程序中一个非常慢的Restful接口,在该接口中,除 requests 之外,所有不允许软件包都不允许导入。 >

多线程多进程的速度仅限于GIL和将在其中运行程序的4核计算机。

我知道您可以通过生成器并生成关键字在Python 2.7中实现不完整的协程,但是我如何才能使用不完整的协程功能来执行数千个请求呢?

示例

url_list = ["https://www.example.com/rest?id={}".format(num) for num in range(10000)]
results = request_all(url_list) # do asynchronously

1 个答案:

答案 0 :(得分:1)

首先,您从错误的前提开始。

  • 多处理的速度完全不受GIL的限制。
  • 多处理的速度仅受CPU限制工作的核心数限制,而您的不受此限制。而且异步对于CPU限制的工作根本不起作用,因此多处理将比异步好4倍,而不是更差。
  • 多线程处理的速度仅受GIL限制,CPU绑定的代码不受限制。
  • 多线程的速度几乎不受内核数量的影响。如果您的代码是CPU绑定的,则线程最终大多在单个内核上进行序列化。但同样,异步在这里甚至更糟,而不是更好。

人们使用async的原因不是因为它可以解决所有这些问题;实际上,这只会使他们变得更糟。主要优点是,如果您有大量的工人几乎不做任何工作,则可以比一吨的等待线程或进程更便宜地调度一堆等待协程。第二个优点是,您可以将选择器循环与调度程序循环绑定,并消除协调它们的一些开销。


第二,首先不能将requestsasyncio一起使用。它期望能够在套接字读取时阻止整个线程。有一个项目要围绕基于asyncio的传输适配器来重写它,但是它没有完成就被废弃了。

通常的解决方法是在线程中使用它,例如与run_in_executor一起使用。但是,如果您唯一要做的事情是requests,那么建立一个仅将事件分派给线程池执行程序的事件循环是很愚蠢的;只需直接使用执行程序即可。


第三,我怀疑您实际上是否需要并行运行数千个请求。尽管详细信息当然取决于您的服务或网络或瓶颈,但拥有一个线程池可以并行运行(例如,并行运行12或64个请求,而其他数千个在它们后面排队)几乎总是更有效的

处理成千上万的并发连接(以及工作线程)通常只需要在服务器上完成即可。有时,您必须在聚合来自大量不同服务的数据的客户端上执行此操作。但是,如果您只使用一种服务,那么这么多的并发几乎永远不会有任何好处。


第四,如果您确实确实希望在Python 2中使用基于协程的事件循环,到目前为止,最简单的方法是使用geventgreenlets或其他此类库。

是的,它们为您提供了一个事件循环,隐藏在您看不见的幕后,以及“神奇”的协程,其中,屈服发生在socket.sendThread.join之类的方法中,而不是显式的可以在awaityield from上看到,但好的一面是它们已经可以工作了,实际上,魔术意味着它们可以在requests上工作,而您构建的任何东西都不会。

当然,您不想使用任何第三方库。在Stackless或PyPy之上自己构建类似greenlets的东西非常容易;为CPython构建它需要做很多工作。然后,您仍然必须完成gevent所做的所有猴子补丁操作,以使sockets之类的库像魔术一样工作,或者在显式的greenlet周围重写requests


无论如何,如果您真的想仅在普通yield上构建事件循环,就可以。

Greg Ewing's original papers on why Python needed to add yield from中,他提供了一个仅包含yield的协程事件循环的示例,以及一个更好的示例,该示例使用显式蹦床yield进行了一个简单的网络驱动示例。他甚至从yield from到Python 3.1的代码中编写了一个自动翻译器。

请注意,必须从蹦床上弹尽所有力量,否则事情的效率就会大大降低。真的没有办法解决。这是我们使用yield from语言的一个很好的原因。

但这只是带有玩具网络的调度程序部分。您仍然需要集成selectors循环,然后编写协程以替换所需的所有socket函数。考虑一下asyncio花费Guido的时间,当他从内到外都了解Python并可以使用yield from时……但是您可以窃取他的大部分设计,因此它不会完全不好。尽管如此,这仍然需要大量工作。

(哦,而且您在Python 2中没有selectors。如果您不关心Windows,可以很容易地从select模块中构建所需的部分,但是如果您确实关心Windows,则还有很多工作要做。)

请记住,由于requests无法与您的代码一起使用,因此您还需要重新实现大部分 it 。或者,也许更好,将aiohttpasyncio移植到您的框架。

最后,我很乐意为您提供一个结果,即结果的效率不会像Python 3中的aiohttp或{{1} {1}}(在Python 2中),或者只是requests在任一线程池中。

当然,您将成为世界上唯一使用它的人。 gevent有数百个bug可以在requests和stdlib之间进行修复,这些错误仅是由于数十位早期采用者(包括对此类专家很认真的人)对其进行抨击而被发现。并且asynciotuliprequests等都被成千上万台服务器使用,这些服务器处理着价值数十亿美元的业务,因此,您将受益于所有发现错误并需要修复的人们。无论您构建哪种产品,几乎都肯定不会像任何一种解决方案那样可靠。

所有这些对于您可能仍然需要移植到Python 3的事情而言,因为Python 2的生命周期不到一年半,并且发行版和第三方库已经脱离它。举一个相关的例子,aiohttp 3.0将至少需要Python 3.5。如果您想坚持使用Python 2.7,那么将永远坚持使用gevent 2.1。