Asyncio与Gevent

Dan*_*tik 18 python gevent python-asyncio

背景

我曾经在一个Python2系统上工作,该系统具有许多同步编写的自定义I / O代码,并使用线程进行了扩展。在某个时候,我们无法进一步扩展它,并意识到我们必须切换到异步编程。

  • Twisted是最受欢迎的选择,但我们想避免其回调地狱。
  • 它确实具有@inlineCallbacks装饰器,该装饰器与其他一些库一样,使用生成器魔术有效地实现了协程。那是可以忍受的,但感觉有点片状。
  • 然后我们找到了gevent。您要做的只是:
from gevent import monkey
monkey.patch_all()
Run Code Online (Sandbox Code Playgroud)

就像那样,您的所有标准I / O(套接字,数据库事务以及用纯Python编写的所有内容)实际上都是异步的,使用greenlet产生并在后台切换。

这不是完美的:

  • 那时,它在Windows上运行不佳(并且今天仍然有一些限制)。幸运的是,我们在Linux上运行。
  • 它不能猴子补丁C扩展,因此,例如,我们不能使用MySQLdb。幸运的是,有许多纯Python替代品,例如PyMySQL。

如今,Python 3更加流行,并且有了它-asyncio。就个人而言,我认为这很棒,但是最近有人问我,它比我们使用gevent做的更好吗?无法给出足够好的答案。

这听起来可能是主观的,但是我实际上是在寻找一个实际的用例,其中一个将大大优于另一个,或者允许另一个不这样做。到目前为止,这是我收集的注意事项:

  1. 就像我说的那样,gevent在Windows上非常受限制。再说一次,我所知道的大多数生产代码都在Linux上运行。

    如果需要在Windows上运行,请使用asyncio

  2. Gevent无法猴子C扩展。但是,asyncio无法猴子修补任何东西

    想象一下,出现了一种新的数据库技术,并且您想使用它,但是它没有一个纯Python库,因此您无法将其与Gevent集成。问题是,当没有可以与asyncio集成的io *库时,您就像被困一样!当然,有工作线程和执行程序,但这不是重点,无论如何在两种情况下都一样好。

  3. 有人说这是个人喜好,但我认为可以说同步编程从本质上讲要比异步编程更容易(请考虑一下:您是否遇到过可以使用套接字的新手程序员,但是很难理解如何正确选择/投票,或考虑期货/承诺?您遇到过相反的情况吗?)。

    无论如何,我们不要去那里。我想解决这一点,因为它经常出现(这是关于reddit的讨论),但是我真正要关注的是您有实际理由使用其中一种的情况。

  4. Asyncio是标准库的一部分。这是巨大的:这意味着它得到了很好的维护,良好的记录,并且每个人都知道它并默认使用它。

    但是,考虑到您需要使用Gevent的知识很少(并且它也得到很好的维护和记录),它似乎并不重要。因此,即使对于涉及期货的最复杂的方案,StackOverflow上也有多个答案,但根本不使用期货的可能性似乎也是可行的。

那么:asyncio盛行的一些具体用例是什么?当然,Guido和Python社区有充分的理由对此投入大量精力,甚至在语言中引入了新的关键字-我似乎找不到它们。

Sla*_*lam 5

实际使用情况中的“简单”答案:

  1. 关于gevent的好处-您可以打补丁,这意味着[理论上]您可以使用同步库。即,您可以修补django。
  2. 关于gevent的坏事-并非所有补丁都可以打补丁,如果您必须使用一些无法打补丁的数据库驱动程序,那么您将注定要失败
  3. 关于gevent的最糟糕的事情-这是“神奇的”。了解“ patch_all”会发生什么的工作量是巨大的,同样的工作也适用于为您的开发团队寻找/雇用新人。更糟糕的是-调试基于gevent的代码是地狱。我会说,与回调几乎一样,即使不是更糟。

我认为稍后一点很关键。软件工程中最被低估的是代码只能被读取,编写或有效运行(如果以后是这种情况,您宁愿从python切换到系统级语言)。Asyncio缺少用于异步编程的部分- 预定义和受控的上下文切换点。您实际上是在编写同步代码(即,您不是在考虑突然的线程切换,锁,队列等),await ...而是在知道调用受到IO阻塞的情况下使用,因此您可以让事件循环选择适合CPU的其他功能,稍后再获取当前状态。

这就是使asyncio如此出色的原因-易于维护。不利之处在于,几乎所有“世界”也必须是异步的-数据库驱动程序,http工具,文件处理程序。有时您会缺少库,这是可以保证的。

  • 我不确定我是否理解比较。你不必了解 `patch_all` 做什么,就像你必须了解 `asyncio` 是如何实现的一样;您只需编写简单、可读和可维护的同步代码(并对其进行调试),然后神奇地使其异步。我错过了什么? (8认同)
  • 这不是最糟糕的事情,这就是它应该如何运作。Golang 中的一切都是异步的,并且它可以正常工作。在Python中,有多种选择,但所有选择仍然以某种方式被破坏。只需比较 subprocess 和 asyncio.subprocess 即可了解标准库如何提供两种不具有功能奇偶性的相同功能的方法。这就是我所说的破碎。 (4认同)
  • 是的,不。所有 async 所做的就是在各处添加 async/await ,并且您会遇到完全相同的问题,试图解决协程的执行问题。拥有所有额外的标记并不会使其更具可读性或易于理解。更好的方法是简单地识别并记录异步入口点。 (3认同)
  • 您不能以与同步代码相同的方式调试补丁代码补丁代码,这就是重点。在单用户负载下运行 gevent-patched 代码,比如说,100k 是不同的——就上下文何时以及如何切换而言。asyncio 的实现(这肯定很难)与理解事件循环的概念不同。后来就很简单了。 (2认同)