FastAPI:(1)并发async与await
FastAPI:(1)并发async与await
由于CSDN无法展示「渐构」的「#d,#e,#t,#c,#v,#a」标签,推荐访问我个人网站进行阅读:Hkini
「渐构展示」如下:
#c 概述 文章内容
文章围绕 FastAPI 的并发与异步机制展开,系统讲解了以下几个关键概念及其关系:
-
I/O 密集 vs CPU 密集:帮助选择合适的并发模型。
-
并发 vs 并行:揭示异步与多核处理的本质区别。
-
进程、线程、协程:比较三者在资源、调度、开销上的异同。
-
async/await 语法:结合 FastAPI 实例,演示如何高效处理并发请求。
1.I/O密集与CPU密集
#d I/O密集型
「I/O密集型」 是指一种程序或任务,其执行过程中主要时间花费在输入/输出操作上,而不是在中央处理器(CPU)上的计算。这类任务通常频繁地等待外部设备(如磁盘、网络、键盘等)的数据传输完成,而在此期间CPU处于空闲状态。
特征包括:
-
高频I/O操作:任务频繁进行输入/输出操作。
-
CPU利用率低:程序运行时CPU计算时间占比小,大部分时间在等待I/O完成。
-
等待时间主导:整体执行时间由I/O延迟决定,而非计算复杂度。
-
依赖外部资源:性能瓶颈在于外部设备的速度,而非CPU处理速度。
#e 文件复制(正例) I/O密集型
现象:“用户将一个大文件从硬盘A复制到硬盘B”。
「该现象」是「I/O密集型」。 因为复制过程需要大量读取源文件并写入目标位置,这两个动作都属于I/O操作,而实际CPU参与的运算非常少,整个过程的时间主要取决于硬盘的读写速度。
特征比对:
-
高频I/O操作:多次读写硬盘。
-
CPU利用率低:仅做简单缓冲处理,CPU几乎不忙碌。
-
等待时间主导:耗时主要来自硬盘读写延迟。
-
依赖外部资源:速度受限于硬盘性能。
#e 网络爬虫(正例) I/O密集型
现象:“一个简单的网页抓取程序下载多个网站页面内容”。
「该现象」是「I/O密集型」。 因为程序的主要工作是通过网络请求获取数据,而这些请求往往需要等待服务器响应和网络传输,CPU仅用于解析URL和存储结果,利用率极低。
特征比对:
-
高频I/O操作:频繁发起HTTP请求和接收响应。
-
CPU利用率低:解析和存储开销小。
-
等待时间主导:网络延迟占总时间90%以上。
-
依赖外部资源:受网络带宽和服务器响应速度限制。
#e 图像渲染(反例) I/O密集型
现象:“使用GPU进行高分辨率图像渲染”。
「该现象」不是「I/O密集型」。 因为图像渲染主要是复杂的数学计算和图形处理,虽然可能有少量I/O操作(如加载纹理),但整体执行时间主要消耗在计算上,CPU/GPU利用率极高。
特征比对:
-
高频I/O操作:基本没有频繁I/O。
-
CPU利用率低:不符合,GPU/CPU高度参与。
-
等待时间主导:不符合,时间由计算主导。
-
依赖外部资源:性能瓶颈在计算单元而非I/O。
#c 作用 I/O密集型1
判断一个任务是否是「I/O密集型」, 有助于选择合适的并发模型:I/O 密集型任务大多数时间在等待外部设备(如磁盘、网络、数据库),而不是执行计算。也有助于优化系统资源使用: 如果任务是 I/O 密集的,那么系统瓶颈在于磁盘/网络延迟,而非 CPU。 此时加快 CPU 并不能带来性能提升,应优化:网络带宽、连接数、代理池;数据库查询索引、连接池配置;使用缓存(如 Redis)减少 I/O 请求频率等等。
#d CPU密集
主要由 计算资源(CPU) 决定运行效率的任务,任务中的瓶颈在于数据处理、数学运算或逻辑推理过程,而不是 I/O 等待。
重要特征:
特征 | 说明 |
---|---|
高 CPU 使用率 | 长时间紧密使用 CPU 执行逻辑、计算、算法等操作 |
少 I/O 等待 | 极少依赖文件、网络等外部数据源,主要处理内部计算 |
线程并行效果好 | 多核 CPU 上可通过多线程/多进程并行显著加速 |
性能瓶颈在计算 | 降低计算复杂度、优化算法比提升网络速度更关键 |
#e 计算机视觉 CPU密集
一幅图像由数百万像素组成,每个像素有3种颜色值,处理通常需要同时对这些像素进行计算。
特征 | 分析 |
---|---|
高 CPU 使用率 | ✔ 图像处理任务如边缘检测、滤波、特征提取等需逐像素计算,持续占用 CPU。 |
少 I/O 等待 | ✔ 图像通常一次性加载进内存后处理,之后操作集中于内存数据,无需频繁磁盘或网络读写。 |
线程并行效果好 | ✔ 图像像素之间高度独立,可划分为块,多个线程/核并行处理不同区域显著加速。 |
性能瓶颈在计算 | ✔ 计算速度决定总处理时间,瓶颈通常是像素处理、卷积操作等数学运算,而非外部 I/O。 |
#e 机器学习 CPU密集
它通常需要大量的"矩阵"和"向量"乘法。想象一个包含数字的巨大电子表格,并同时将所有数字相乘。
特征 | 分析 |
---|---|
高 CPU 使用率 | ✔ 模型训练/预测过程涉及大规模矩阵乘法、线性代数计算,强依赖 CPU 浮点运算能力。 |
少 I/O 等待 | ✔ 数据一般预先载入内存,训练或预测过程中主要进行内存计算,不涉及频繁 I/O。 |
线程并行效果好 | ✔ 可用多线程并行矩阵乘法(如 NumPy、BLAS 库),在多核系统上效果好。 |
性能瓶颈在计算 | ✔ 计算复杂度随着数据规模、特征维度上升而指数增长,I/O 通常不是主要限制因素。 |
#e 深度学习 CPU密集
这是机器学习的一个子领域,同样适用。只是没有一个数字的电子表格可以相乘,而是一个庞大的数字集合,在很多情况下,你需要使用一个特殊的处理器来构建和使用这些模型。
特征 | 分析 |
---|---|
高 CPU 使用率 | ✔ 虽然深度学习常借助 GPU,但在没有 GPU 时,模型前向/反向传播会强占 CPU 运算资源。 |
少 I/O 等待 | ✔ 训练时数据常常预加载或使用 DataLoader 异步读取,计算部分主要在内存中完成。 |
线程并行效果好 | ✔ 网络层之间/样本之间存在天然并行性,可通过线程/多进程或 GPU 并行执行。 |
性能瓶颈在计算 | ✔ 深度神经网络的训练(如 CNN/RNN)主要受限于矩阵乘、梯度更新等计算量,而不是 I/O。 |
#c 作用 CPU密集型1
判断一个任务是否属于「CPU密集型」,有助于
-
选择正确的并发与并行模型:CPU 密集型任务主要消耗的是处理器资源,适合采用多进程(multiprocessing) 或原生线程进行**真正的并行计算。
-
做出合适的部署资源选择:应优先选择高主频、多核 CPU 的机器,甚至考虑使用 HPC(高性能计算)集群。例如:AWS c7g 实例(Graviton CPU,适合计算密集任务),GCP N2D 实例(AMD EPYC,适合并行计算)。
2.并行与并发
#d 异步代码
「异步代码」 是指在程序执行过程中,允许某些操作在“后台”进行而不阻塞主线程或后续代码执行的编程模式。这种机制通常用于处理耗时任务(如网络请求、文件读写等),以避免程序因等待任务完成而冻结。
特征包括:
-
非阻塞:异步代码不会阻塞主线程或其他操作的执行。
-
并发性:多个任务可以并发执行,而不是顺序执行。
-
回调/承诺/async-await:通过回调函数、Promise 对象或 async/await 语法来处理异步操作的结果。
-
耗时任务:常用于处理需要较长时间完成的任务,如 I/O 操作、网络请求等。
#e async/await函数(正例) 异步代码
现象:
async function getData() {
const response = await fetch('https://api.example.com/data'); const data = await response.json();
console.log(data); }
「该现象」是「异步代码」。 因为它使用了 async/await
语法来处理异步操作,虽然看起来像同步代码,但底层仍是基于 Promise 的异步机制。
特征比对:
-
非阻塞:函数内部的
await
不会阻塞主线程。 -
并发性:可与其他异步任务并发执行。
-
使用异步机制:使用了
async/await
。 -
处理耗时任务:网络请求属于典型耗时任务。
#e Promise请求(正例) 异步代码
现象:
fetch('https://api.example.com/data').then(response => response.json()).then(data => console.log(data)
);
「该现象」是「异步代码」。 因为它使用了 Promise 来发起网络请求,并通过 .then()
来处理响应结果,整个过程不会阻塞主线程。
特征比对:
-
非阻塞:主线程继续执行其他任务,不等待请求完成。
-
并发性:请求与其它操作并行。
-
使用异步机制:使用了
Promise
和.then()
。 -
处理耗时任务:网络请求通常是耗时操作。
#d 并行
并行(Parallelism) 是一种在同一时刻(同时)运行多个任务或指令的能力,通常依赖于多个处理单元(如多核 CPU、多个计算节点)来同时执行多个工作。
特征包括:
-
同时发生:多个任务在物理时间上真正同时运行。
-
独立性:任务之间可以独立执行,不需要等待彼此完成。
-
资源共享:任务共享硬件资源(如CPU核心、内存)。
-
无序执行:任务的执行顺序可能不确定,由调度器决定。
#e 多核处理程序(正例) 并行
现象:“一台四核计算机同时运行浏览器、音乐播放器、文档编辑器和杀毒软件”。
「该现象」是「并行」。 因为四个不同的程序分别被分配到四个核心中,各自独立运行,没有互相等待,共享同一台设备的资源。
特征比对:
-
同时发生:四个程序同时运行。
-
独立性:各程序之间互不影响。
-
资源共享:共享CPU、内存等硬件资源。
-
无序执行:执行顺序由操作系统调度决定。
#e GPU渲染图像(正例) 并行
现象:“图形处理器(GPU)在渲染一幅高清图像时,将图像划分为多个像素块,每个像素块由一个线程处理”。
「该现象」是「并行」。 因为每个像素块的处理任务相互独立,可以同时进行,且由GPU统一调度。
特征比对:
-
同时发生:多个像素块同时被处理。
-
独立性:每个像素块的处理与其他无关。
-
资源共享:共享GPU的运算单元和显存。
-
无序执行:像素块的处理顺序由GPU调度器决定。
#c 作用 并行1
判断一个任务是否属于「并行」,可以帮助做以下决策:
- 是否能有效提升性能(缩短总运行时间)
-
并行的核心目的就是用多个处理单元并行执行多个独立任务,从而显著减少总耗时。
-
若任务可并行,理论上可以做到“线性加速”:用 4 个核心让程序运行时间减少到 1/4。
- 决定是否要进行任务划分(task decomposition)
-
如果任务能并行,应将其拆分为多个独立、可同时运行的子任务(子进程/线程)。
-
如图像渲染可分区处理,矩阵乘法可按行列块拆分,数据清洗可并发批处理。
#d 并发
在一个时间段内,有多个任务“同时”进行,这些任务通过交替调度、共享资源的方式,在逻辑上并行执行,虽然它们不一定在物理上同时运行。
特征包括:
-
任务重叠:多个任务在同一时间段被管理和推进,可能交错执行。
-
交替调度:单核 CPU 上的任务快速切换,看似同时进行。
-
共享资源:任务可能操作同一个数据结构或设备,需考虑同步问题。
#e 餐厅多桌点餐(正例) 并发
一个服务员面对 10 桌客人点餐,他:
-
每桌问完一句就去下一个桌子记录 。
-
然后统一提交厨房。
-
再在等菜期间处理其他桌的请求。
特征对比:
-
任务重叠:服务员一个人不能“同时”服务所有人。
-
交替调度:但他在一个时间段里交替处理多个桌子的请求。
#e 单核运行多程序(正例) 并发
现象:在电脑上打开音乐播放器(后台播放音乐),同时使用 Word 编辑文档,电脑只有一个 CPU 核心。
特征对比:
✅ 任务重叠:播放器和编辑器两个程序逻辑上“同时进行”。
✅ 交替调度:操作系统为每个进程分配时间片,快速切换 CPU 资源。
✅ 共享资源:两个程序可能共享内存、磁盘、音频硬件等资源,需 OS 协调访问顺序与同步。
#c 作用 并发1
判断一个任务是否属于「并发」,可以帮助做以下决策:
-
是否可以提升任务的响应性与吞吐量:如果希望系统即使在处理慢任务时也能继续响应其他请求,就需要采用并发模型(如异步 I/O、线程池等)。
-
是否可以使用异步编程替代多线程:对于 I/O 密集型任务(如请求接口、访问磁盘/数据库),并发编程(如
asyncio
)可以减少线程使用,避免线程调度开销。
#d 并行vs并发
概念 | 定义 |
---|---|
并行(Parallelism) | 一种在同一时刻(同时)运行多个任务或指令的能力,通常依赖于多个处理单元(如多核 CPU、多个计算节点)来同时执行多个工作。 |
并发(Concurrency) | 在一个时间段内,有多个任务“同时”进行,这些任务通过交替调度、共享资源的方式,在逻辑上并行执行,虽然它们不一定在物理上同时运行。 |
特征 | 并行 | 并发 |
---|---|---|
执行方式 | 同时执行 | 交替执行(可能共享时间段) |
硬件要求 | 需要多核或多处理器支持 | 单核也可实现 |
时间关系 | 真正的同时性 | 表面的同时性(实际是交错执行) |
目标 | 提升处理速度和吞吐量 | 提高响应性和资源利用率 |
典型例子 | 多线程矩阵运算 | 浏览器同时加载页面与播放音频 |
#e 多任务浏览器 并行vs并发
现象:一个现代浏览器同时执行网页渲染、JavaScript脚本执行和视频播放。
该现象中的并发体现为:浏览器通过操作系统调度,在同一时间段内切换不同任务(如渲染、脚本、视频),即使在单核CPU上也能实现。
该现象中的并行体现为:如果浏览器使用多个线程分别处理渲染、脚本和视频,并且这些线程在不同的CPU核心上同时运行,则体现了真正的并行。
#e 单线程任务调度 并行vs并发
现象:一个单线程程序依次执行A、B、C三个任务,每个任务完成后才开始下一个。
「该现象」是并发。因为虽然任务顺序执行,但在宏观时间段上它们“看起来”是在重叠执行(如通过时间片轮转模拟)。
如果是「并行」,则「该现象」需修改为:任务A、B、C被分配到不同的CPU核心上同时执行,而不是顺序进行。
#e 多核同时计算 并行vs并发
现象:两个独立的图像滤镜算法在双核CPU上分别由两个核心独立执行。
「该现象」是并行。因为两个任务在不同核心上真正同时执行,具备物理层面的同时性。
如果是「并发」,则「该现象」需修改为:两个任务在同一个核心上通过时间片轮流执行,虽能完成相同功能,但不具备真正的同时性。
#c 应用 Web与机器学习
使用 FastAPI,可以充分利用 Web 开发中常见的并发机制(这是 Node.js 吸引力的重要来源),实现高效的请求处理与响应调度。
同时,借助 Python 原生支持的多进程机制,FastAPI 也能很好地应对机器学习系统中常见的CPU 密集型任务,实现真正的并行计算,显著提升模型推理、批量处理等操作的性能。
再加上 Python 本身是数据科学和机器学习(尤其是深度学习)领域的主导语言,这使得 FastAPI 成为构建数据科学/机器学习 Web API 和应用程序的理想选择,兼顾响应效率与计算能力。
3.进程,线程,协程
#d 进程
进程是操作系统中资源分配和调度的基本单位,是程序在某个数据集上的一次执行活动。它不仅包含可执行程序的代码,还包括程序执行过程中所需的资源,如内存、文件句柄、CPU时间、进程状态等。每个进程在运行时都是独立的,拥有自己的地址空间,并且可以通过进程间通信(IPC)与其他进程交互。
重要特征:
-
独立性:每个进程拥有自己的私有资源(如地址空间),彼此之间互不干扰。
-
动态性:进程是程序的运行实例,具有生命周期(创建、就绪、运行、阻塞、终止)。
-
并发性:多个进程可同时存在于系统中,并可能并发执行(尤其在多核系统上)。
-
资源拥有者:进程拥有系统资源(如打开的文件、内存段、网络连接等)。
-
调度单位:操作系统调度的是进程而不是程序,决定其在CPU上的执行。
#e 客户端电子邮件(正例) 进程
现象: 用户打开一个邮件客户端程序(如Outlook),系统会为此创建一个进程实例来管理界面显示、网络通信、数据处理等任务。
特征对比:
-
独立性:该进程有自己的内存空间和文件句柄,与其他程序(如浏览器)隔离。
-
动态性:用户启动客户端时创建进程,关闭时进程终止。
-
并发性:用户可同时运行多个程序,每个都是一个独立进程。
-
资源拥有者:该进程拥有网络连接、缓存文件、图形资源等。
-
调度单位:操作系统调度该进程,使其在空闲时间段运行。
#e 后台数据库服务(正例) 进程
现象: 一个运行中的数据库服务器(如MySQL或PostgreSQL)通常以服务进程的形式常驻内存,持续响应客户端请求。
特征对比:
-
独立性:数据库进程拥有自己的内存缓存、连接池,与网页服务器进程相互隔离。
-
动态性:服务器启动后创建进程并在运行期间保持活跃;服务器停止后进程销毁。
-
并发性:可以和Web服务器、缓存系统等进程并发运行。
-
资源拥有者:拥有访问磁盘、管理连接的权限。
-
调度单位:操作系统分配CPU资源以响应数据库请求。
#e 代码例子(正例) 进程
import os
import time
from multiprocessing import Process, current_processdef show_info(name, delay):"""子进程任务函数,展示进程独立性、资源拥有者特性"""# 每个进程有独立的PID和父PIDprint(f"子进程{name}启动,PID: {os.getpid()},父PID: {os.getppid()}")# 每个进程拥有自己的资源(如变量、内存空间)local_var = f"我是{name}的私有变量"print(f"子进程{name}的私有变量地址: {id(local_var)}")time.sleep(delay) # 模拟任务,体现并发性print(f"子进程{name}结束,PID: {os.getpid()}")if __name__ == '__main__':print(f"主进程启动,PID: {os.getpid()}")processes = []for i in range(3):# 动态创建进程,体现进程的动态性p = Process(target=show_info, args=(f'#{i+1}', 2)) #Process的target参数指定子进程要执行的函数,args是传递给函数的参数processes.append(p)p.start()print(f"主进程:已启动子进程{i+1},PID: {p.pid}")for p in processes:p.join() #join表示等待子进程结束print("所有子进程已结束,主进程退出。")
'''
主进程启动,PID: 29016
主进程:已启动子进程1,PID: 23600
主进程:已启动子进程2,PID: 27296
主进程:已启动子进程3,PID: 8612
子进程#1启动,PID: 23600,父PID: 29016
子进程#1的私有变量地址: 2757712138512
子进程#2启动,PID: 27296,父PID: 29016
子进程#2的私有变量地址: 1798449844496
子进程#3启动,PID: 8612,父PID: 29016
'''
#e HTML文件本身(反例) 进程
现象: 一个静态HTML文件(如index.html
)只是存储在磁盘上的代码文本文件,即使它代表一个网页,也不能单独构成进程。
特征对比:
-
独立性(缺失):HTML文件不具有资源隔离,它本质上是数据,而非运行实体。
-
动态性(缺失):HTML文件不会“运行”,没有生命周期,除非被浏览器加载解释。
-
并发性(缺失):多个HTML文件可被打开,但本身并不并发执行任何任务。
-
资源拥有者(缺失):文件本身不拥有资源,而是被其他进程(如浏览器)访问。
-
调度单位(缺失):不是系统调度的单位,不会出现在进程列表中。
#d 线程
线程是程序执行中的最小单位,是进程中的一个执行流。一个进程可以包含多个线程,这些线程共享进程的资源(如内存地址空间、文件句柄等),但每个线程拥有自己的执行栈、程序计数器和寄存器。线程的引入是为了提高程序的并发性和资源利用率,是操作系统调度的基本单位(在现代系统中)。
重要特征:
-
轻量级性:线程比进程更轻量,线程间切换开销小,共享资源无需复杂通信。
-
共享性:同一进程内的所有线程共享内存空间和系统资源。
-
并发性:多个线程可并发执行,提高程序响应速度。
-
独立执行栈:每个线程有自己的栈空间和程序执行状态。
-
依赖性:线程依赖于进程而存在,不能脱离进程独立存在。
#e 浏览器渲染与加载(正例) 线程
现象: 现代浏览器(如Chrome)在加载一个网页时会创建多个线程:主线程处理页面渲染,IO线程处理网络请求,JavaScript线程执行脚本等。
特征对比:
-
轻量级性:多个线程协同处理页面加载任务,相比启动多个浏览器进程开销更小。
-
共享性:线程共享浏览器进程的内存,如DOM结构、缓存等。
-
并发性:JS执行、图片加载、用户输入可并行进行,提高用户体验。
-
独立执行栈:每个线程管理自己任务的调用栈。
-
依赖性:线程依赖于浏览器主进程运行,一旦主进程崩溃,线程全部终止。
#e 代码例子 线程
-
单线程版本顺序执行,总耗时约5秒
-
多线程版本并发执行,总耗时约1秒
-
直观展示线程的并发优势
-
注意:在CPU密集型任务中可能表现不同
import time
import threadingdef task():time.sleep(1)# 单线程版本
start = time.time()
for _ in range(5):task()
print(f"单线程耗时: {time.time()-start:.2f}秒")# 多线程版本
start = time.time()
threads = []
for _ in range(5):t = threading.Thread(target=task)threads.append(t)t.start()for t in threads:t.join()
print(f"多线程耗时: {time.time()-start:.2f}秒")'''
单线程耗时: 5.00秒
多线程耗时: 1.00秒
'''
#d 协程
协程是一种比线程更轻量的用户级并发执行单元。它通过用户态的调度机制在多个任务之间切换,通常用于执行异步、非阻塞的操作。协程与线程不同,它们并不由操作系统调度,而是由程序自身控制的,可以在单线程内实现高并发。协程的运行可以被挂起和恢复,非常适合处理 I/O 密集型任务。
重要特征:
-
轻量级切换:协程在用户态切换,开销极小,不需要内核态上下文切换。
-
可挂起与恢复:协程可通过
yield
、await
等语法挂起并在合适时机恢复执行。 -
单线程并发:在单线程中实现并发执行,避免线程切换开销和锁竞争。
-
协作式调度:协程的切换依赖显式的让出控制权,非抢占式。
-
状态保持能力:协程可在挂起时保留上下文状态,恢复后从中断处继续运行。
#e 游戏NPC 协程
现象: 在游戏开发中,NPC(非玩家角色)常用协程来执行连续动作,如“巡逻 → 停顿 → 追击 → 返回”。通过协程可以在游戏主线程中以异步方式调度角色行为而不阻塞其他逻辑。
特征对比:
-
轻量级切换:无需创建线程,多个角色行为同时进行,调度代价小。
-
可挂起与恢复:巡逻状态执行到一定条件后可挂起,待触发恢复后继续。
-
单线程并发:所有角色行为在主线程协程中处理,避免多线程同步问题。
-
协作式调度:每个角色逻辑明确让出控制权,在主循环中协作切换。
-
状态保持能力:挂起时保留角色行为状态,恢复后自然过渡。
#e Web中的异步IO 协程
现象: 现代高并发Web服务器(如基于Python的FastAPI
)通过协程处理HTTP请求,实现同时处理成千上万个客户端连接,而不会为每个请求创建线程。
特征对比:
-
轻量级切换:协程处理每个请求时的切换无需线程/进程切换。
-
可挂起与恢复:等待数据库或网络响应时挂起,资源得以释放,响应后继续。
-
单线程并发:整个服务器可运行在一个线程中,支持高并发。
-
协作式调度:协程通过
await
控制切换,不会强制打断。 -
状态保持能力:请求处理过程中的上下文状态可以保留在协程内部。
#e 代码例子(正例) 协程
下面是使用FastAPI框架,实现协程:
from fastapi import FastAPI
import asyncio
import time# 创建 FastAPI 应用实例
app = FastAPI()# 异步I/O模拟函数,使用协程方式实现非阻塞等待
async def simulated_io(duration: float = 1.0):# 异步休眠指定秒数,模拟I/O操作await asyncio.sleep(duration)return {"message": f"Waited {duration:.2f} seconds"}# 路由:模拟处理请求的异步接口
@app.get("/work/{duration}")
async def handle_request(duration: float):# 记录开始时间start = time.time()# 调用异步I/O模拟函数result = await simulated_io(duration)# 计算耗时elapsed = time.time() - start# 返回结果,包括处理方式和耗时return {"result": result,"handled_by": "coroutine","elapsed_seconds": round(elapsed, 4)}# 根接口,返回服务信息
@app.get("/")
async def root():return {"info": "FastAPI coroutine-based high concurrency server"}# 启动服务器命令(在终端中运行):
if __name__ == "__main__":import uvicorn# 启动 FastAPI 应用,监听 0.0.0.0:8000,单进程模式uvicorn.run(app, host="0.0.0.0", port=8000, workers=1)
#d 进程线程协程
特征 | 进程 | 线程 | 协程 |
---|---|---|---|
资源隔离性 | 完全隔离 | 共享所属进程资源 | 共享所在线程资源 |
切换开销 | 大(涉及上下文和内存切换) | 小(仅需切换寄存器和栈) | 极小(用户态操作,无系统调用) |
并发能力 | 多进程并行 | 同一进程内多线程并发 | 同一线程内多协程协作 |
通信机制 | 需要IPC等机制 | 直接访问共享内存 | 直接访问共享变量 |
控制权 | 由操作系统管理 | 由操作系统调度 | 由用户代码控制 |
#e Web服务器处理请求 进程线程协程
现象:一个Web服务器使用多进程模型启动多个服务实例;每个实例内部创建多个线程来处理客户端连接;每个线程中运行多个异步IO操作的协程以提高吞吐量。
该现象中的进程是Web服务器启动的多个服务实例,因为它们各自拥有独立的内存空间和资源。
该现象中的线程是每个服务实例中用于处理连接的执行单元,因为它们共享同一进程的地址空间,并可并发执行。
该现象中的协程是线程内部用于处理异步IO的任务,因为它们在同一线程中协作式运行,切换成本极低。
#e 独立运行的应用程序 进程线程协程
现象:一个命令行工具在终端中运行,占用独立的内存空间,不与其他程序共享资源。
「该现象」是「进程」。因为该工具作为独立实体运行,具有自己的地址空间和系统资源。
如果是「线程」,则「该现象」需修改为:该工具作为另一个程序的一部分运行,共享其地址空间,因为线程必须依附于进程存在。
如果是「协程」,则「该现象」需修改为:该工具内部通过协作式调度执行多个子任务,且不依赖操作系统调度,因为协程是由用户控制的任务。
#e 多线程下载器 进程线程协程
现象:一个文件下载器程序使用多个线程同时下载不同部分,所有线程共享同一进程的堆内存。
「该现象」是「线程」。因为这些执行单元共享同一进程的地址空间,并能并发执行任务。
如果是「进程」,则「该现象」需修改为:每个下载任务运行在独立进程中,彼此之间不共享内存,因为进程必须具备独立资源。
如果是「协程」,则「该现象」需修改为:下载任务按顺序协作式执行,且由用户代码控制切换,因为协程强调协作而非抢占。
#e 异步网络爬虫 进程线程协程
现象:一个Python脚本使用asyncio库实现多个HTTP请求的并发发起,所有请求由事件循环驱动,在单一线程中运行。
「该现象」是「协程」。因为这些任务由用户代码控制调度,在单一线程中协作式运行,切换开销极小。
如果是「进程」,则「该现象」需修改为:每个请求运行在独立进程中,拥有自己的内存空间,因为进程要求资源隔离。
如果是「线程」,则「该现象」需修改为:多个线程并发执行请求,但由操作系统调度,因为线程是操作系统级别的并发单元。
4.async与await
#d async与await
在 FastAPI 中,async
和 await
是 Python 原生的异步编程语法,用于构建非阻塞式 I/O 处理流程。通过将路由函数声明为 async def
并在其中使用 await
调用异步操作(如数据库、API 调用、文件I/O),FastAPI 可以在单线程内高效并发处理成千上万个 HTTP 请求,从而显著提升 Web 应用在 I/O 密集场景下的性能与伸缩性。
重要特征:
-
非阻塞性:
await
表达式会挂起当前协程,让出控制权,从而使事件循环可以运行其他任务,实现并发。 -
协程声明语义:使用
async def
声明函数表示这是一个协程,只能通过await
执行其异步结果。 -
高并发处理能力:无需为每个请求创建线程或进程,协程使得一个线程可以处理大量请求。
-
节省资源:相比线程,每个协程几乎不占用额外内存,切换开销低。
-
错误传播与异常处理清晰:协程函数在逻辑上仍是顺序执行,便于调试与错误处理。
#e Web请求聚合服务 async与await
现象 :一个汇总天气、股市、新闻的 Web API 服务通过 FastAPI 实现。使用 async def
定义路由函数,内部调用三个异步接口(天气、金融、资讯),并使用 await
同时挂起等待其结果。
特征对比
-
✅ 非阻塞性:多个请求并发时,不会互相等待。
-
✅ 协程语义:所有异步接口使用
await
。 -
✅ 高并发能力:成千上万个请求可在单线程中流畅处理。
-
✅ 符合 FastAPI 模式:充分利用事件循环模型。
'''
使用 httpx.AsyncClient 实现 HTTP 请求异步化。
asyncio.gather 并发运行多个外部接口请求。
接口 /aggregate 模拟真实业务聚合,如获取天气、股票、新闻等多源数据。
'''
from fastapi import FastAPI
import httpx
import asyncioapp = FastAPI()# 模拟三个外部接口
EXTERNAL_APIS = {"weather": "https://httpbin.org/delay/1","stocks": "https://httpbin.org/delay/1","news": "https://httpbin.org/delay/1"
}async def fetch_data(name: str, url: str):async with httpx.AsyncClient() as client:response = await client.get(url)return {name: response.status_code}@app.get("/aggregate")
async def aggregate_data():tasks = [fetch_data(name, url) for name, url in EXTERNAL_APIS.items()]results = await asyncio.gather(*tasks)merged = {}for item in results:merged.update(item)return {"aggregated_status": merged}
#e 文件上传处理系统 async与await
现象: 用户上传大文件至云端,FastAPI 路由函数异步读取上传内容,并通过 await
将数据块流式写入异步云存储 API(如 S3、OSS)。
特征对比
-
✅ 非阻塞:I/O(磁盘、网络)不阻塞服务器响应。
-
✅ 协程函数:整个处理链
async def
,资源占用极低。 -
✅ 节省资源:不占用线程池,避免线程阻塞。
-
✅ 错误处理清晰:协程异常可统一捕获处理。
'''
使用 UploadFile + aiofiles 实现异步文件写入,避免阻塞线程。
FastAPI 内部自动处理流式上传过程,适合大文件、高并发上传。
无需多线程即可处理多个用户同时上传。
'''
from fastapi import FastAPI, UploadFile, File
import aiofilesapp = FastAPI()@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):file_location = f"./uploaded_{file.filename}"async with aiofiles.open(file_location, 'wb') as out_file:content = await file.read()await out_file.write(content)return {"filename": file.filename, "status": "uploaded successfully"}