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