当前位置: 首页 > 后端技术 > Python

[翻译]同步vs.异步Python:有什么区别?

时间:2023-03-25 20:18:55 Python

Translation同步vs.异步Python:有什么区别?您听说过异步Python代码比“正常”(或同步)Python代码更快吗?怎么会这样?在本文中,我将尝试解释异步是什么以及它与普通Python代码的区别。同步和异步是什么意思?Web应用程序通常需要在短时间内处理许多来自不同客户端的请求。为避免处理延迟,它们必须能够并行处理多个请求(通常称为并发)。在本文中,我将继续使用Web应用程序作为示例,但请记住,还有其他类型的应用程序也可以从多任务处理中受益,因此本讨论不针对特定的Web应用程序。术语“同步”和“异步”指的是编写使用并发的应用程序的两种方式。所谓的“同步”服务器使用线程和进程的底层操作系统支持来实现这种并发性。下面是同步部署的呈现:在这种情况下,我们有五个客户端,都向应用程序发送请求。此应用程序的公共访问点是Web服务器,它充当负载平衡器,将请求分发给一组服务器工作者,这些工作者可以实现为进程、线程或两者的组合。工作人员执行负载均衡器分配给他们的请求。您可以在Flask或Django等Web应用程序框架中编写应用程序逻辑,它存在于这些worker中。这种方案非常适合多CPU的服务器,因为你可以配置worker的数量为CPU数量的倍数,通过这种配置,可以实现核的均匀利用,这对于单个Python来说是不可能的process是的,因为全局解释器锁(GIL)强加了一些限制。在缺点方面,上图清楚地显示了这种方法的主要局限性。我们有5个客户,但只有4个工人。如果这5个客户端同时发送请求,而负载均衡器只能向每个worker派发1个请求,则没有竞争worker的请求将留在队列中,等待worker可用。因此,5个客户中有4个会及时收到回复,但其中1个需要等待更长时间。让服务器表现良好的关键是选择合适数量的工作人员,以防止或尽量减少给定预期负载的阻塞请求实例。异步服务器设置更难绘制,但这是我最好的选择:这种类型的服务器在由循环控制的单个进程中运行。Loop是一个非常高效的任务管理器和调度器,它创建任务来处理客户端发送的请求。与长时间运行的服务器工作进程不同,循环创建一个异步任务来处理特定请求,该请求在该请求完成时被销毁。在任何给定时间,异步服务器可能有数百甚至数千个活动任务,所有任务都由一个循环管理并同时执行它们的工作。您可能想知道异步任务之间的并行性是如何实现的。这是有趣的部分,因为异步应用程序完全依赖协作式多任务处理。这意味着什么?当任务需要等待外部事件(例如来自数据库服务器的响应)时,它不会像同步工作者那样等待,而是告诉循环要等待什么,然后将控制权返回给循环。然后循环可以找到另一个准备运行但被数据库阻塞的任务。最终数据库将发送一个响应,此时循环将认为第一个任务准备好再次运行,并将尽快恢复它。异步任务暂停和恢复执行的这种能力在相对抽象的层面上很难理解。为了帮助您将此应用于您可能已经知道的事情,请考虑在Python中执行此操作的一种方法是使用await或yield关键字,但这不是唯一的方法,稍后您将看到。异步应用程序完全在单个进程和单个线程中运行的方式令人惊奇。当然,这种并发需要一定的纪律,因为不能让任务在CPU上停留太久,否则剩下的任务会饿死。为了异步工作,所有任务都需要自动挂起并及时将控制权返回给循环。为了受益于异步风格,应用程序需要执行通常是I/O阻塞且不需要太多CPU工作的任务。Web应用程序通常很适合,尤其是当它们需要处理大量客户端请求时。为了在使用异步服务器时最大化多个CPU的利用率,通常会创建一个混合解决方案,添加一个负载均衡器并在每个CPU上运行一个异步服务器,如下图所示:一种方式我确定你要知道,要用Python编写异步应用程序,您可以使用asyncio包,它构建在协程之上以实现所有异步应用程序所需的挂起和恢复功能。关键字yield以及较新的async和await是asyncio构建异步功能的基础。总而言之,Python生态系统中还有其他基于协程的异步解决方案,例如Trio和Curio。还有Twisted,它是最古老的协作框架,甚至早于asyncio。如果您对编写异步Web应用程序感兴趣,有许多基于协程的异步框架可供选择,包括aiohttp、sanic、FastAPI和Tornado。许多人不知道的是,协程只是Python中可用于编写异步代码的两种方式之一。第二种方法基于一个名为greenlet的包,您可以使用pip安装它。Greenlets类似于协程,它们也允许Python函数暂停执行和稍后恢复执行,但实现方式完全不同,这意味着Python中的异步生态系统分为两大类。用于异步开发的协程和greenlet之间一个有趣的区别是,前者需要Python语言的特定关键字和功能才能工作,而后者则不需要。我的意思是,基于协程的应用程序需要使用非常特定的语法来编写,而基于greenlet的应用程序看起来与普通的Python代码完全一样。这真的很酷,因为它允许同步代码在特定条件下异步执行,这对于基于协程的解决方案(如asyncio)来说是不可能的。那么,就greenlet而言,asyncio等价物是什么?我知道三个基于greenlet的异步包:Gevent、Eventlet和Meinheld,尽管最后一个更像是一个Web服务器,而不是一个通用的异步库。它们都有自己的异步循环实现,并提供了一个有趣的“猴子修补”功能,该功能用greenlets上实现的等效非阻塞版本替换了Python标准库中的阻塞函数,例如那些进行网络和线程处理的函数。如果您有一段同步代码希望异步运行,这些包很可能会让您这样做。你会对此感到惊讶。据我所知,唯一明确支持greenlets的Web框架是Flask。当您在greenlet网络服务器上运行时,该框架会自动检测并相应地进行调整,而无需任何配置。执行此操作时,需要注意不要调用阻塞函数,否则,请使用monkey-patching来“修复”那些阻塞函数。然而,Flask并不是唯一可以从greenlets中获益的框架。其他不了解greenlet的Web框架,例如Django和Bottle,在与greenletweb服务器配对时也可以异步运行,并且monkey-patches修复了阻塞功能。异步比同步快吗?对于同步和异步应用程序的性能存在广泛的误解。异步应用程序被认为比同步应用程序快得多。让我澄清一下,以便我们达成统一的理解。不管Python代码是同步编写还是异步编写,其运行速度都是完全一样的。除了代码之外,还有两个因素会影响并发应用程序的性能:上下文切换和可伸缩性。上下文切换在所有正在运行的任务之间公平共享CPU所需的工作(称为上下文切换)会影响应用程序性能。对于同步应用程序,这项工作由操作系统完成,基本上是一个没有配置或微调选项的黑盒子。对于异步应用程序,上下文切换是通过循环完成的。asyncio提供的默认循环实现是用Python编写的,效率不高。uvloop包提供了一个部分用C代码实现的替代循环,以获得更好的性能。Gevent和Meinheld使用的事件循环也是用C代码编写的。Eventlet使用用Python编写的循环。高度优化的异步循环在执行上下文切换时可能比操作系统更有效,但根据我的经验,您必须以非常高的并发级别运行才能看到切实的性能提升。对于大多数应用程序,我认为同步和异步上下文切换之间的性能差异不会很大。可扩展性我认为异步更快的神话源于这样一个事实,即异步应用程序通常能够更有效地使用cpu,因为它们比同步具有更好的扩展性和更灵活的方法。考虑一下如果上面显示的同步服务器同时收到100个请求会发生什么。服务器一次不能处理超过4个请求,因此大多数请求将在队列中等待一段时间,然后才能分配工作人员。将其与异步服务器进行比较,异步服务器一次创建100个任务(如果使用混合模型,则4个异步worker每个将创建25个任务)。使用异步服务器,所有请求都可以开始处理而无需等待(尽管公平地说,可能还有其他瓶颈会减慢速度,例如对活动数据库连接数的限制)。如果这100个任务是CPU密集型任务,那么同步和异步解决方案将具有相似的性能,因为CPU以固定速度运行,Python始终以相同速度执行代码,应用程序做相同的工作。但是,如果任务需要执行大量的I/O操作,那么只有4个并发请求,同步服务器可能无法获得高CPU利用率。另一方面,异步服务器在保持CPU繁忙方面肯定更好,因为它并行运行所有100个请求。您可能想知道为什么不能运行100个同步工作者以使两个服务器具有相同的并发性。考虑到每个工作人员都需要有自己的Python解释器,以及与之关联的所有资源,以及具有自己资源的应用程序的单独副本。您的服务器和应用程序的大小将决定您可以运行多少个工作实例,但通常这个数字不是很高。另一方面,异步任务非常轻量级,并且都在单个工作进程的上下文中运行,因此它们具有明显的优势。牢记这一点,只有在以下情况下我们才能说异步比同步更快:高负载(没有高负载就没有高并发的好处)任务是I/O绑定的(如果任务是CPU绑定的,那么超过CPU并发以上该数字没有帮助)您可以查看每单位时间处理的平均请求数。如果您查看单个请求处理时间,您将看不到巨大差异,异步甚至可能会稍微慢一些,因为有更多并发任务竞争CPU。我希望本文能消除一些关于异步代码的困惑和误解。我希望你们牢记这两点:在高负载下,异步应用程序只会比同步应用程序做得更好感谢greenlets,即使您编写纯代码并使用Flask或Django等传统框架也能从异步中受益如果如果您想了解有关异步系统如何工作的更多信息,请查看我的PyCon演示文稿AsynchronousPythonfortheCompleteBeginner。