关于Python的Asyncio vs. Gevent

Asyncio vs. Gevent

背景

我曾经在一个python2系统上工作过,这个系统有很多自定义的I/O代码是同步编写的,并且使用线程进行缩放。在某种程度上,我们无法进一步扩展它,并且意识到我们必须切换到异步编程。

  • 扭曲是流行的选择,但我们想避免它的回调地狱。
  • 它确实有@inlineCallbacks装饰器,它和其他一些库一样,使用generator magic有效地实现协程。这是可以容忍的,但感觉有点不对劲。
  • 然后我们找到了Gevent。你所要做的就是:
1
2
from gevent import monkey
monkey.patch_all()

就像这样,所有的标准I/O——套接字、数据库事务、所有用纯Python编写的东西——都是异步的,使用greenlet在后台生成和切换。

它并不完美:

  • 当时,它在Windows上运行得不好(现在它仍然有一些限制)。幸运的是,我们在Linux上运行。
  • 它不能猴补丁C扩展,所以我们不能使用mysqldb,例如。幸运的是,有很多纯python替代品,比如pymysql。

问题

现在,python 3更受欢迎,并且与它一起使用——asyncio。就我个人而言,我认为这很好,但最近有人问我,这比我们对Gevent所做的更好吗,我想不出一个足够好的答案。

这听起来可能是主观的,但我实际上在寻找真正的用例,其中一个会显著优于另一个,或者允许另一个不允许的东西。以下是迄今为止我收集到的注意事项:

  • 就像我说的,gevent在窗户上相当有限。另外,我所知道的大多数生产代码都在Linux上运行。

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

  • gevent不能猴补丁C扩展。但是,Asyncio不能猴子修补任何东西。

    假设出现了一种新的数据库技术,您希望使用它,但是没有一个纯粹的Python库,所以您不能将它与gevent集成。问题是,当没有可以与Asyncio集成的IO*库时,您就陷入了困境!当然,也有工作线程和执行器,但这不是重点,而且在这两种情况下都工作得很好。

  • 有些人说这是个人爱好的问题,但我认为可以公平地说,同步编程本质上比异步编程更容易(想想看:你有没有遇到过一个可以使用套接字的初学者,但是很难理解如何正确地选择/轮询它们,或者思考未来/承诺?你见过反面吗?).

    不管怎样,我们不要去那里。我想解决这一点,因为它经常出现(这里有一个关于reddit的讨论),但我真正想要的是场景,其中你有一个实际的理由使用其中一个。

  • Asyncio是标准库的一部分。这是巨大的:这意味着它得到了很好的维护,有很好的文档记录,并且每个人都知道它,并且在默认情况下使用它。

    但是,考虑到使用gevent所需的知识很少(而且它也得到了很好的维护和文档记录),它似乎没有那么重要。因此,尽管在StackOverflow上有多个关于期货的最复杂场景的答案,但根本不使用期货的可能性似乎同样可行。

  • 那么:异步盛行的一些具体用例是什么?毫无疑问,guido和python社区有充分的理由投入这么多精力,甚至在语言中引入新的关键字——我似乎找不到它们。


    实际使用的"简单"答案:

  • gevent的好处是,您可以修补一些东西,这意味着您[理论上]可以使用同步库。也就是说,你可以给Django打补丁。
  • Gevent的坏处-不是所有的东西都可以修补,如果你必须使用一些无法修补的DB驱动程序,你就注定了
  • 格凡特最糟糕的事情-它是"神奇的"。理解"patch-all"所发生的事情需要付出大量的努力,同样的努力也适用于为您的开发团队寻找/雇用新的人员。更糟糕的是,调试基于gevent的代码简直是地狱。我想说,跟回拨差不多,如果不是更糟的话。
  • 我认为,以后的观点才是关键。在软件工程中,最被低估的是代码是要被读取的,而不是有效地编写或运行的(如果以后是这样的话,那么您宁愿从Python转换到系统级语言)。Asyncio缺少用于异步编程的部分-预定义的和控制的上下文切换点。您实际上编写了同步代码(即不考虑突然的线程切换、锁、队列等),并且在知道调用是IO阻塞时使用await ...,因此您让事件循环拾取其他准备好用于CPU的内容,并在稍后获取当前状态。

    这就是Asyncio如此优秀的原因——它很容易维护。缺点是几乎所有的"世界"都必须是异步的——DB驱动程序、HTTP工具、文件处理程序。有时你会错过图书馆,这是很有保证的。