如今许多应用,特别是当今的 Web 应用,严重依赖于 I/O(输入/输出)操作。这类操作包括从互联网下载网页内容、通过网络与一组微服务通信,或对 MySQL、Postgres 等数据库同时运行多个查询。一个网络请求或与微服务的通信可能需要数百毫秒,如果网络慢的话甚至要几秒。数据库查询可能会很耗时,尤其是当数据库负载较高或查询复杂时。一个 Web 服务器可能需要同时处理成百上千个请求。
如果一次进行大量这样的 I/O 请求,可能会导致严重的性能问题。如果我们像在顺序运行的应用程序中那样逐个运行这些请求,就会看到性能影响的叠加。例如,如果我们编写一个需要下载 100 个网页或运行 100 个查询的应用程序,每个操作需要 1 秒执行,那么我们的应用程序至少需要 100 秒才能运行完。然而,如果我们利用并发性同时开始下载和等待,理论上,我们可以在短短 1 秒内完成这些操作。
asyncio 最早在 Python 3.4 中引入,作为除多线程和多进程之外处理这些高并发工作负载的另一种方式。合理使用这个库可以极大地提高使用 I/O 操作的应用程序的性能和资源利用率,因为它允许我们同时启动许多这些长时间运行的任务。
在本章中,我们将介绍并发的基础知识,以更好地理解如何用 Python 和 asyncio 库实现它。我们将探讨 CPU 密集型工作和 I/O 密集型工作之间的区别,以了解哪种并发模型最适合我们的具体需求。我们还将学习进程和线程的基础知识,以及 Python 全局解释器锁对并发带来的独特挑战。最后,我们将了解如何利用称为非阻塞 I/O 的概念和事件循环,仅使用一个 Python 进程和线程来实现并发。这是 asyncio 的主要并发模型。
在同步应用程序中,代码顺序运行。前一行代码完成后,后一行代码才会运行,一次只做一件事。对于许多(即使不是大多数)应用程序来说,这种模型效果不错。但是,如果某行代码特别慢呢?那样的话,这行慢代码之后的所有其他代码都会被卡住,等待该行完成。这些可能很慢的代码行会阻塞应用程序运行任何其他代码。我们很多人都曾在有缺陷的用户界面中见过这种情况,我们愉快地点击着,直到应用程序冻结,给我们留下一个旋转器或无响应的用户界面。这是应用程序被阻塞导致糟糕用户体验的一个例子。
任何操作如果耗时足够长都可能阻塞应用程序,但许多应用程序会因等待 I/O 而被阻塞。I/O 指的是计算机的输入和输出设备,如键盘、硬盘,最常见的是网卡。这些操作等待用户输入或从基于 Web 的 API 检索内容。在同步应用程序中,我们将卡在等待这些操作完成,然后才能运行其他任何东西。这可能导致性能和响应性问题,因为我们只能在任何给定时间运行一个长时间操作,而且该操作会阻止我们的应用程序做其他任何事情。
解决这个问题的一个办法是引入并发性。最简单地说,并发意味着允许同时处理多个任务。在并发 I/O 的情况下,例子包括允许同时发出多个 Web 请求,或允许同时连接到 Web 服务器。
在 Python 中有几种方法可以实现这种并发性。Python 生态系统最近新增的功能之一是 asyncio 库。asyncio 是 异步 I/O 的简称。它是一个 Python 库,允许我们使用异步编程模型运行代码。这使我们能够同时处理多个 I/O 操作,同时保持应用程序的响应性。
那么什么是异步编程?它意味着特定的长时间运行任务可以在后台与主应用程序分开运行。与其阻塞所有其他应用程序代码等待那个长时间运行的任务完成,系统可以自由执行不依赖于该任务的其他工作。然后,一旦长时间运行的任务完成,我们会收到完成的通知,以便处理结果。
在 Python 3.4 版本中,asyncio 首次引入,伴随着装饰器和生成器的 yieldfrom 语法来定义协程。协程是一个可以在我们有一个可能长时间运行的任务时暂停,然后在任务完成时恢复的方法。在 Python 3.5 版本中,当关键字 async 和 await 被明确添加到语言中时,语言实现了对协程和异步编程的一流支持。这种语法常见于其他编程语言,如 C# 和 JavaScript,它允许我们使异步代码看起来像是同步运行的。这使得异步代码易于阅读和理解,因为它看起来像是大多数软件工程师熟悉的顺序流程。asyncio 是一个库,使用称为单线程事件循环的并发模型来异步执行这些协程。
尽管 asyncio 的名称可能让我们认为这个库只适用于 I/O 操作,但它也具备通过多线程和多进程互操作来处理其他类型操作的功能。通过这种互操作性,我们可以将 async 和 await 语法与线程和进程一起使用,使这些工作流程更容易理解。这意味着这个库不仅适用于基于 I/O 的并发,也可以用于 CPU 密集型代码。为了更好地理解 asyncio 可以帮助我们处理哪些类型的工作负载,以及哪种并发模型最适合每种并发类型,让我们探讨一下 I/O 和 CPU 密集型操作的区别。