uvicorn多worker模式仅在cpu有余量、存在同步阻塞或计算密集型逻辑、且未被gpu内存瓶颈限制时才真正有效;盲目增加workers数量反而降低吞吐,必须结合cpu核心数、内存容量和请求模式合理配置,并避免gunicorn配置冲突、--reload启用、容器cpu配额不足等常见失效场景。

直接上结论:Uvicorn 多 worker 模式不是“加数字就变快”,它只在特定条件下真正起效——CPU 核心有余量、应用含同步阻塞或计算密集型逻辑、且未被 GPU 内存或模型加载瓶颈卡住。盲目设 --workers 16 很可能让吞吐不升反降。
什么时候必须用 --workers?
单进程 Uvicorn 能靠 async/await 高效处理成百上千 I/O 请求,但一旦出现以下任一情况,就必须考虑多 worker:
- 你用了
time.sleep()、requests.get()、pandas.read_csv()等同步阻塞调用,哪怕只有一处,整个事件循环就卡死 - 你的业务逻辑包含 CPU 密集操作(如图像预处理、文本向量化、模型推理前的特征工程)
- 压测时 CPU 利用率长期低于 60%,但 QPS 上不去,说明单进程无法吃满多核
- 你部署的是 M2LOrder 这类 GPU 推理服务,单 worker 只能串行发 batch,GPU 显存和算力大量闲置
--workers 数值怎么定?别套公式,看资源
所谓 “2 × CPU 核数 + 1” 是误导性经验。真实决策应基于三件事:
-
CPU 核心数:用
nproc查,4 核机器设--workers 4是安全起点;超过 8 个 worker 后吞吐增长通常趋缓 - 内存总量:每个 worker 是独立 Python 进程,加载一次模型就占一份显存/CPU 内存。RTX 3060(12GB)最多扛住 4 个 worker,再多会 OOM
- 请求模式:如果请求极不均匀(比如每分钟来 100 个大 batch,其余时间空闲),worker 数宁少勿多,避免空转开销
示例命令:uvicorn app:app --workers 4 --host 0.0.0.0 --port 8000
常见踩坑:workers 设了,但没生效
启动后 htop 只看到 1 个 uvicorn 进程?大概率掉进这几个坑:
- 你在用 Gunicorn 做前置负载,却忘了写
worker_class="uvicorn.workers.UvicornWorker"—— 缺这个,Gunicorn 默认起的是同步 worker,async def路由直接报TypeError: 'coroutine' object is not callable - 你同时写了
gunicorn ... --workers 4和uvicorn ... --workers 4,两个参数冲突,Uvicorn 启动失败或静默退回到单进程 - 你在容器里跑,但没给容器分配足够 CPU quota,Linux cgroups 限死了实际可用核心数,
--workers 8实际只跑得动 2 个 - 你用了
--reload(开发模式),该参数强制禁用多 worker,只允许--workers 1
GPU 推理服务要额外注意 batch_size
对 M2LOrder 这类服务,光加 worker 不够。每个 worker 独立加载模型,若 batch_size=1,GPU 利用率必然低迷。必须配合动态 batch:
- 在 FastAPI 路由里做请求聚合(例如用
asyncio.Queue缓存几毫秒内的请求,凑够 batch 再 infer) - 不要让每个 worker 自己维护 batch state —— 进程隔离导致状态不一致,要用 Redis 或共享内存协调
- 监控
nvidia-smi的util%和memory-usage,目标是稳定在 70%~90%,而不是单纯堆 worker 数
最容易被忽略的一点:多 worker 下,所有全局变量(比如缓存字典、数据库连接池)都变成 per-process 副本。你以为的“共享缓存”,其实是 N 份重复数据。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











