同步与异步Python有何不同?( 二 )


Python 生态系统中还有其它基于协程的异步方案,例如 Trio 和 Curio。还有 Twisted,它是所有协程框架中最古老的,甚至出现得比asyncio都要早 。
如果你对编写异步 Web 应用程序感兴趣,有许多基于协程的异步框架可以选择,包括 aiohttp 、 sanic 、 FastAPI 和 Tornado。
很多人不知道的是,协程只是 Python 中编写异步代码的两种方法之一 。第二种方法是基于一个叫做 greenlet 的库,你可以用 pip 安装它 。Greenlets 和协程类似,它们也允许一个 Python 函数暂停执行并稍后恢复,但是它们实现这点的方式完全不同,这意味着 Python 中的异步生态系统分成两大类 。
协程与 greenlets 之间针对异步开发最有意思的区别是,前者需要 Python 语言特定的关键字和特性才能工作,而后者并不需要 。我的意思是,基于协程的应用程序需要使用一种特定的语法来书写,而基于 greenlet 的应用程序看起来几乎和普通 Python 代码一样 。这非常酷,因为在某些情况下,这让同步代码可以被异步执行,这是诸如asyncio之类的基于协程的方案做不到的 。
那么在 greenlet 方面,跟asyncio对等的库有哪些?我知道 3 个基于 greenlet 的异步包: Gevent 、 Eventlet 和 Meinheld,尽管最后一个更像是一个 Web 服务器而不是一个通用的异步库 。它们都有自己的异步循环实现,而且它们都提供了一个有趣的“monkey-patching”功能,取代了 Python 标准库中的阻塞函数,例如那些执行网络和线程的函数,并基于 greenlets 实现了等效的非阻塞版本 。如果你有一些同步代码想要异步运行,这些包会对你有所帮助 。
据我所知,唯一明确支持 greenlet 的 Web 框架只有 Flask。这个框架会自动监测,当你想要运行在一个 greenlet Web 服务器上时,它会自我进行相应调整,而无需进行任何配置 。这么做时,你需要注意不要调用阻塞函数,或者,如果你要调用阻塞函数,最好用猴子补丁来“修复”那些阻塞函数 。
但是,Flask 并不是唯一受益于 greenlets 的框架 。其它 Web 框架,例如 Django 和 Bottle,虽然没有 greenlets,但也可以通过结合一个 greenlet Web 服务器并使用 monkey-patching 修复阻塞函数的方式来异步运行 。
异步比同步更快吗?对于同步和异步应用程序的性能,存在着一个广泛的误解——异步应用程序比同步应用程序快得多 。
对此,我需要澄清一下 。无论是用同步方式写,还是用异步方式写,Python 代码运行速度是几乎相同的 。除了代码,有两个因素能够影响一个并发应用程序的性能:上下文切换和可扩展性 。
上下文切换在所有运行的任务间公平地共享 CPU 所需的工作,称为上下文切换,能够影响应用程序的性能 。对同步应用程序来说,这项工作是由操作系统完成的,而且基本上是一个黑箱,不需要配置或微调选项 。对异步应用程序来说,上下文切换是由循环完成的 。
默认的循环实现由asyncio提供,是用 Python 编写的,效率不是很高 。而 uvloop 包提供了一个备选的循环方案,其中部分代码是用 C 编写的来实现更好的性能 。Gevent 和 Meinheld 所使用的事件循环也是用 C 编写的 。Eventlet 用的是 Python 编写的循环 。
高度优化的异步循环比操作系统在进行上下文切换方面更有效率,但根据我的经验,要想看到实际的效率提升,你运行的并发量必须非常大 。对于大部分应用程序,我不认为同步和异步上下文切换之间的性能差距有多明显 。
扩展性我认为异步更快这个神话的来源是,异步应用程序通常会更有效地使用 CPU、能更好地进行扩展并且扩展方式比同步更灵活 。
如果上面示意图中的同步服务器同时收到 100 个请求,想一下会发生什么 。这个服务器同时最多只能处理 4 个请求,因此大部分请求会停留在一个队列中等待,直到它们被分配一个 worker 。
与之形成对比的是,异步服务器会立即创建 100 个任务(或者使用混合模式的话,在 4 个异步 worker 上每个创建 25 个任务) 。使用异步服务器,所有请求都会立即开始处理而不用等待(尽管公平地说,这种方案也还会有其它瓶颈会减慢速度,例如对活跃的数据库连接的限制) 。
如果这 100 个任务主要使用 CPU,那么同步和异步方案会有相似的性能,因为每个 CPU 运行的速度是固定的,Python 执行代码的速度总是相同的,应用程序要完成的工作也是相同的 。但是,如果这些任务需要做很多 I/O 操作,那么同步服务器只能处理 4 个并发请求而不能实现 CPU 的高利用率 。而另一方面,异步服务器会更好地保持 CPU 繁忙,因为它是并行地运行所有这 100 个请求 。


推荐阅读