你听过有人说异步Python代码比“普通(或同步)Python代码”快吗?真的是那样吗?
“同步”和“异步”是什么意思?Web应用程序通常在短时间内处理来自不同客户端的许多请求。为了避免处理延迟,需要考虑并行处理多个请求,这通常称为“并发”。
在本文中,我将继续使用Web应用程序作为示例,但是请记住,还有其他类型的应用程序也可以从并发完成多个任务中受益,因此本文的讨论不仅仅针对Web应用程序。
术语“同步”和“异步”指的是编写并发应用程序的两种方式。所谓的“同步”服务器使用底层操作系统支持的线程和进程来实现这种并发性。以下是同步部署的示意图:
在本例中,我们有5个客户端,它们都向应用程序发送请求。此应用程序的访问门户是一个Web服务器,它通过将服务分配给一组服务器工作人员来充当负载平衡器,这些工作人员可以实现为进程、线程或两者的组合。这些工作者执行负载平衡器分配给他们的请求。您Web应用程序框架(Baxter,如Flask或Django)编写的应用程序逻辑在这些worker中运行。
这种类型的方案更适合具有多个CPU的服务器,因为您可以将工作线程的数量设置为CPU的数量,这样您就可以平衡地利用您的处理器内核,由于全局解释器锁(GIL)的限制,单个Python进程无法实现这一点。
在缺点方面,上面的示意图也清楚地显示了这种方案的主要局限性。我们有五个客户,但只有四个工人。如果所有五个客户端同时发送请求,负载均衡器会将除一个客户端之外的所有请求发送到工作池,而其余的请求必须保留在队列中,等待工作线程变得可用。因此,五分之四的请求会立即得到答复,而剩下的五分之一则需要等待一段时间。服务器优化的关键之一是选择适当数量的工作器,以防止或最小化给定预期负载的请求阻塞。
异步服务器的配置很难画出来,但我会尽力而为:
这种类型的服务器在单个进程中运行,由一个循环控制。这个循环是一个非常有效的任务管理器和调度器,创建任务来执行客户端发送的请求。与长期存在的服务器工作线程不同,异步任务是由循环创建的,用于处理特定的请求,当请求完成时,任务也会被销毁。在任何时候,一个异步服务器都会有成百上千个活动任务,所有这些任务都会在循环管理下执行自己的工作。
您可能想知道异步任务之间的并行性是如何实现的。这是有趣的部分,因为异步应用程序通过独特的协作多任务处理来实现这一点。这是什么意思?当任务需要等待外部事件(例如,来自数据库服务器的响应)时,它不会像同步工作器那样等待,而是告诉循环它需要等待什么,然后将控制权交还给它。当这个任务被数据库阻塞时,循环可以找到另一个就绪任务。最后,数据库将发送一个响应,然后循环将认为第一个任务准备好再次运行,并将尽快恢复它。
暂停和恢复异步任务的能力可能难以抽象理解。为了帮助您将它应用到您已经知道的内容中,您可以考虑在Python中使用await或yield关键字,但是您稍后会发现这并不是实现异步任务的唯一方法。
令人惊讶的是,异步应用程序完全在单个进程或线程中运行。当然,这种类型的并发需要遵循一些规则,所以不能让一个任务占用CPU太久,否则剩下的任务会被阻塞。为了异步执行,所有任务都需要定期暂停,并将控制权交还给循环。为了从异步模式中受益,应用程序需要有经常被I/O阻塞的任务,并且没有太多的CPU工作。Web应用程序通常非常适合,尤其是当它们需要处理大量客户端请求时。
使用异步服务器时,为了最大限度地利用多个CPU,通常需要创建一个混合方案,添加一个负载平衡器,并在每个CPU上运行一个异步服务器,如下图所示:
Python 中实现异步的 2 种方法我相信你知道,用Python写异步应用,可以用asyncio包,它在co-process的基础上实现了所有异步应用所需要的暂停和恢复特性。yield关键字以及更新的async和await是asyncio异步功能的基础。
Python生态中还有其他基于协同学的异步方案,比如Trio和Curio。而作为所有协同框架中最古老的Twisted,甚至比asyncio出现的还要早。
如果你对编写异步Web应用感兴趣,有很多基于协同学的异步框架可以选择,包括aiohttp、sanic、FastAPI和Tornado。
很多人不知道的是,协程只是用Python编写异步代码的两种方法之一。第二种方法是基于一个名为greenlet的库,您可以用pip安装它。Greenlets类似于coroutine,它们也允许Python函数暂停执行并在稍后恢复,但它们以完全不同的方式实现这一点,这意味着Python中的异步生态系统分为两类。
协同学和greenlets在异步开发方面最有趣的区别在于,前者需要Python语言特有的关键字和特性才能工作,而后者则不需要。我的意思是,基于协同学的应用程序需要用特定的语法编写,而基于greenlet的应用程序看起来几乎就像普通的Python代码。这非常酷,因为在某些情况下,它允许同步代码异步执行,这是基于协同进程的方案(如asyncio)无法做到的。
那么在greenlet方面,有哪些相当于asyncio的库呢?我知道3个基于greenlet的异步包:Gevent、Eventlet和Meinheld,虽然最后一个更像是Web服务器而不是一般的异步库。它们都有自己的异步循环实现,并且都提供了一个有趣的“猴子打补丁”功能,替代Python标准库中的阻塞函数,比如执行网络和线程的函数,并基于greenlets实现等价的非阻塞版本。如果您有一些想要异步运行的同步代码,这些包将会帮助您。
据我所知,唯一明确支持greenlet的Web框架是Flask。这个框架会自动监控,当你想在greenlet Web服务器上运行时,它会相应地自我调整,无需任何配置。这样做的时候,需要注意不要调用阻塞函数,或者,如果要调用阻塞函数,最好用monkey patch“修复”那些阻塞函数。
然而,Flask并不是唯一受益于greenlets的框架。其他Web框架,如Django和Bottle,尽管它们没有greenlet,但也可以通过组合一个greenlet Web服务器并使用monkey-patching来修复阻塞功能,从而异步运行。
异步比同步更快吗?对于同步和异步应用程序的性能有一个普遍的误解——异步应用程序比同步应用程序快得多。
我需要澄清一下。无论是同步还是异步编写,Python代码的运行速度几乎相同。除了代码,还有两个因素会影响并发应用程序的性能:上下文切换和可扩展性。
上下文切换在所有正在运行的任务之间公平分配CPU工作,称为上下文切换,会影响应用程序的性能。对于同步应用来说,这个工作是由操作系统完成的,基本上是一个黑盒,不需要配置或者微调选项。对于异步应用程序,上下文切换是通过循环完成的。
的默认循环实现由asyncio提供,用Python编写,效率不是很高。uvloop包提供了一个替代的循环方案,其中一些代码是用C编写的,以获得更好的性能。Gevent和Meinheld使用的事件循环也是用c写的,Eventlet使用的是Python写的循环。
高度优化的异步循环在上下文切换上比操作系统更高效,但是根据我的经验,要看到实际的效率提升,你得运行非常大的并发量。对于大多数应用程序,我认为同步和异步上下文切换之间的性能差距并不明显。
扩展性我认为异步更快这个神话的来源是异步应用程序通常更有效地使用CPU,可以更好地扩展,扩展模式比同步更灵活。
如果上图中的同步服务器同时收到100个请求,想想会发生什么。该服务器最多只能同时处理4个请求,因此大多数请求将留在队列中,等待分配给它们一个工作器。
相比之下,异步服务器将立即创建100个任务(或者如果使用混合模式,在4个异步工作器上各创建25个任务)。对于异步服务器,所有请求都将被立即处理,而无需等待(尽管,公平地说,这种方案中还会有其他瓶颈会降低速度,比如对活动数据库连接的限制)。
如果这100个任务主要使用CPU,那么同步和异步方案会有类似的性能,因为每个CPU都以固定的速度运行,Python总是以相同的速度执行代码,应用要做的工作也是一样的。但是,如果这些任务需要大量的I/O操作,那么同步服务器只能处理四个并发请求,但无法实现高CPU利用率。另一方面,异步服务器将更好地保持CPU忙碌,因为它并行运行所有这100个请求。
您可能想知道为什么不能运行100个同步工作器。然后,两台服务器将具有相同的并发能力。请注意,每个工作者都需要自己的Python解释器和与之相关的所有资源,以及单独的应用程序副本及其资源。您的服务器和应用程序的大小将决定您可以运行多少个worker实例,但通常这个数字不会很大。另一方面,异步任务非常轻量级,并且在单个工作进程的上下文中运行,因此它们具有明显的优势。
综上所述,当只有以下几种场景时,我们可以说异步可能比同步更快:
本文希望解答异步代码的一些困惑和误解。我希望你能记住以下两个要点:
如果你想知道更多关于异步系统如何工作的细节,你可以查看我在YouTube上的PyCon演讲:异步Python完全初学者。
作者介绍:
米格尔·格林伯格是一名软件工程师、摄影师和电影制作人,住在爱尔兰的德拉。你可以在脸书、谷歌、LinkedIn、Github和Twitter上关注他。
原始链接:
https://blog . miguelgrinberg . com/post/sync-vs-async-python-what-is-the-difference
本人也转发此文,发私信“获取资料”即可免费获得价值4999元的InfoQ迷你本。点击文末“了解更多”移步InfoQ官网获取最新资讯~
本文地址:http://www.cangchou.com/9932.html