Uvicorn 不提供信号量控制,需由 FastAPI 等上层框架用 asyncio.Semaphore 实现并发限制;其初值、作用域和异常安全释放必须由开发者严格管理。

Uvicorn 本身不提供信号量控制能力,它只是一个 ASGI 服务器,只负责接收 HTTP 请求、分发给应用(如 FastAPI)、管理连接和生命周期。所有并发控制逻辑必须由上层应用框架或开发者自己实现。
FastAPI 中用 asyncio.Semaphore 控制并发
实际项目中限制并发最常用的方式,是在 FastAPI 路由里手动加 asyncio.Semaphore:
- 它不是 Uvicorn 的功能,但和 Uvicorn 共存于同一异步事件循环中,完全兼容
- 适合控制「同时处理的请求数」,比如防止 GPU 显存被压爆、避免数据库连接池耗尽
- 初值设为
4表示最多 4 个协程能同时执行该段逻辑,其余 await 在 acquire() 上阻塞 - 必须配合
async with semaphore:使用,否则V操作(release)不会自动触发,导致永久卡死
示例片段:
Uvicorn 0.52.1 是 Python ASGI Web 服务器,提供异步应用服务及 WebSocket 支持,常用于 FastAPI、Starlette 等应用的开发和部署。本下载为官方 PyPI 通用 wheel 包,可使用 pip 安装,并由包管理器处理所需依赖。
from fastapi import FastAPI
import asyncio
<p>app = FastAPI()
semaphore = asyncio.Semaphore(4)</p><p>@app.post("/infer")
async def infer():
async with semaphore: # P 操作:阻塞直到有许可</p><h1>这里放模型推理等重资源操作</h1><pre class='brush:python;toolbar:false;'> await asyncio.sleep(2) # 模拟耗时
return {"status": "done"}为什么不能靠 Uvicorn 的 --workers 或 --limit-concurrency
这两个参数常被误认为是“信号量”,但它们作用层级完全不同:
-
--workers N启动 N 个独立进程,每个进程有自己的事件循环和内存空间,asyncio.Semaphore在进程内有效,跨进程不共享 -
--limit-concurrency M是 Uvicorn 自己的连接级限流,基于当前活跃连接数,不感知业务逻辑;它在 ASGI 封装层拦截,请求甚至不会到达你的 FastAPI 路由函数 - 两者都不具备「原子性计数 + 等待队列 + 可编程释放」这些信号量核心语义,无法替代
asyncio.Semaphore
容易踩的坑:初值、作用域与泄漏
信号量失效往往不是 API 用错,而是工程细节失控:
- 初值设错:比如想限制 5 路并发却写了
asyncio.Semaphore(0),结果所有请求永远阻塞 - 作用域太宽:把
semaphore放在模块顶层没问题,但若在每次请求里asyncio.Semaphore(1)新建一个,就完全失去控制意义 - 忘记
async with或漏掉await semaphore.acquire()后的semaphore.release():会导致计数器失准,后续请求全卡住 - 在异常路径中未释放:必须用
try/finally或上下文管理器包裹,否则一次报错就能让信号量永久少一个许可
真正需要信号量的地方,从来不是“有没有”,而是“在哪一层设、初值怎么定、失败时是否还能归还”。Uvicorn 不插手这些,它只确保你写的 asyncio.Semaphore 能跑得起来——剩下的,得你自己盯住每一处 acquire 和 release 的配对。

















