You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
最近由于工作需要,开始了解一些 FastAPI 异步接口的实现,目的是通过异步 I/O 操作提高性能,即在等待外部操作(如数据库查询、网络请求等)完成的同时继续执行其他代码。
关键点:
非阻塞:
await
关键字实现非阻塞等待,让出 CPU 控制权,允许事件循环处理其他任务。任务切换:事件循环在多个异步任务之间高效地切换,确保 CPU 时间被充分利用。
并发执行(Concurrency):即在单个线程中通过事件循环处理多个任务。任务是交替执行的,但 I/O 密集型应用可以显著提高性能。而并行执行(Parallelism)需要多线程或多进程,适用于 CPU 密集型任务。
可见在考虑异步化改造时,一个考量因素是任务是 I/O 密集还是 CPU 密集。
这里有一个异步批处理的例子:
现在回到 FastAPI 的异步接口设计,下面给出的几个简单例子,都会以 I/O 密集型任务为例展开。
并发任务
比如 FastAPI 后端同时向从两个外部 API 发起请求、获取数据,合并后返回给客户端。
fetch_data
使用httpx.AsyncClient
异步地发送 GET 请求到指定的 URL,并返回响应的 JSON 数据。如果请求失败会返回None
并打印错误消息。asyncio.gather
在aggregate_data
路径操作函数中,使用asyncio.gather
来同时发起对两个外部 API 的请求。asyncio.gather
接收多个awaitable
对象(async def
函数),并在其都完成时返回包含所有结果的元组。这允许应用非阻塞地等待多个 I/O 操作的完成,有效利用等待时间来处理其他任务。fetch_data
函数中使用try-except
来捕获可能的异常,并打印错误信息。复杂应用还需要考虑重试逻辑、超时处理等。后台任务
在 FastAPI 中,定义后台任务执行不需要即时完成的操作,比如发送电子邮件、长时间的计算等。这些任务将在请求响应发送给客户端之后异步执行。
数据库访问
对于数据库操作,使用异步库(如
databases
、asyncpg
等)可以提高性能。以
databases
库为例,先安装databases
和对应的数据库驱动,例如,对于 PostgreSQL:然后:
流式响应
即以流的方式逐渐发送响应体给客户端,对于处理大量数据非常有用,例如生成大型 CSV、JSON 文件。
generate_large_json
是生成器,先发送开启数组的[
,然后逐步生成 JSON 对象、以逗号分隔,最后发送关闭数组的]
。数据是分批发送的,但客户端可接收到的完整有效的 JSON 数组。当使用流式响应发送大量数据时,客户端需要处理分批到达的数据。
如果希望边读取边解析 JSON 并动态更新UI,需要在处理字符串时做额外的工作,比如拼接字符串直到有完整的 JSON 对象,解析并更新 UI。
流式响应适用于数据量大到足以影响服务器性能或稳定性的场景。此外还应考虑其他优化措施,如使用压缩、缓存策略或者调整 API 设计来减少单次请求的数据量。
文件操作
对于文件读写操作,可使用
aiofiles
进行异步操作以避免阻塞。先安装aiofiles
:然后:
使用异步缓存
对于频繁访问的资源,使用异步缓存可以减少数据库的查询次数,提高响应速度。可使用
aiocache
库来实现。示例:
CPU 密集怎么办呢?
众所周知 Python 解释器存在 GIL,任何时刻只有一个线程可以在解释器中执行 Python 字节码,这限制了多线程在执行 CPU 密集型任务时的并行性。而多进程开销又很大,适用场景很有限,还有什么办法呢?
其实有时对于非 I/O 操作,多线程带来性能提升也是有可能的:
即使是纯内存数据处理,某些操作也可能会释放 GIL。例如执行密集计算的库函数(如 NumPy 操作)可在执行期间释放 GIL。Python 代码不能并行执行,底层的 C/C++ 库也可以利用多核心并行执行。
多线程下有时虽然没有明显的 I/O 操作,当一个线程因为某些原因不能执行,可能存在微小的等待时间(如等待数据从内存加载到 CPU 缓存),操作系统可执行上下文切换、将 CPU 时间分配给其他线程来执行,宏观上提高运行效率。
现代 CPU 的多级缓存使不同线程可在不同核心上执行,利用各自核心的缓存。处理大量数据时,合理分配数据到不同线程可减少缓存失效,从而提高处理速度。
不同的 Python 解释器(如 CPython 或 PyPy)和数据处理库可能在内部以不同方式处理多线程。一些库可能有针对多线程优化的机制,在 GIL 限制下也能有效利用多核处理器的资源。
在设计理念、工作原理和适用场景上有显著差异,多线程/多进程也是另一种异步实现方式(Future 模式),这里也给出一个例子:
总结
Beta Was this translation helpful? Give feedback.
All reactions