asyncio.Semaphore 如何与限流装饰器结合限并发
技术百科
舞夢輝影
发布时间:2026-01-27
浏览: 次 应使用 async with semaphore 而非手动 acquire()/release(),因其自动异常安全释放;装饰器须为异步函数并接收预创建的 Semaphore 实例,避免新建或闭包共享;FastAPI 中推荐依赖注入替代装饰器。
限流装饰器里直接 await semaphore.acquire() 会出错
因为 acquire() 是协程函数,不能在同步装饰器里直接调用。常见错误是写成 semaphore.acquire()(漏掉 await),结果返回一个协程对象而非布尔值,后续逻辑崩掉;或者强行 await 在普通函数里,触发 RuntimeError: await outside async function。
- 装饰器本身必须是异步的(即返回
async def包裹的协程),或用functools.wraps+asyncio.create_task做调度层包装 - 信号量实例需在事件循环生命周期内共享,不能每次装饰都新建
asyncio.Semaphore(5),否则限流失效 - 推荐把
semaphore作为参数传入装饰器工厂,而非闭包捕获——避免多个装饰器实例误用同一变量引发竞态
用 async def 装饰器工厂实现可配置限流
这是最清晰、易测、可复用的方式。装饰器不自己管理信号量,而是接收一个已初始化的 semaphore 实例:
import asyncio from functools import wrapsdef with_semaphore(semaphore): def decorator(func): @wraps(func) async def wrapper(*args, *kwargs): async with semaphore: # 自动 acquire/release,含异常安全 return await func(args, **kwargs) return wrapper return decorator
全局信号量(按业务场景统一管理)
api_semaphore = asyncio.Semaphore(3)
@wit
h_semaphore(api_semaphore) async def fetch_user(user_id: int): await asyncio.sleep(0.5) return {"id": user_id, "name": "alice"}
-
async with semaphore是唯一推荐方式:它隐式调用acquire()和release(),即使func抛异常也保证释放 - 若需区分不同接口的并发上限(如 /users 限 3,/orders 限 10),就定义多个
semaphore实例,分别传给对应装饰器 - 别在装饰器里做
await asyncio.sleep()或其他 I/O —— 它只负责准入控制,逻辑应下沉到被装饰函数中
FastAPI 路由中用依赖注入更自然
在 FastAPI 这类框架里,硬套装饰器反而绕路。直接用 Depends 注入带信号量的依赖,语义更正、测试更方便:
from fastapi import Depends, FastAPI import asyncioapp = FastAPI() sem = asyncio.Semaphore(5)
async def rate_limited(): async with sem: yield
@app.get("/data") async def getdata( = Depends(rate_limited)): return {"status": "ok"}
- 依赖函数
rate_limited返回的是上下文管理器,Depends会自动await并清理 - 这种方式天然支持作用域(
scope="request"),且可与BackgroundTasks、中间件等正交组合 - 如果需要动态限流(如按用户 token 限频),就别用全局
semaphore,改用字典缓存 per-user 的asyncio.Semaphore实例,但要注意内存泄漏和过期清理
手动 acquire/release 的坑比你想象得多
有人为了“灵活控制”,放弃 async with,改用手动 acquire() + try/finally release()。这在简单场景看似可控,但极易出错:
- 一旦
func内部抛出未捕获异常,finally可能来不及执行,导致信号量永久卡死(死锁) - 协程取消(
asyncio.CancelledError)时,finally不一定运行,release()就丢了 -
acquire()本身可能被取消 —— 它返回一个可等待对象,若在等待期间被 cancel,不会自动回滚计数器 - 结论:除非你真需要在
acquire()后、执行前插入日志或审计逻辑,否则一律用async with。它不是语法糖,是安全边界。
信号量不是魔法,它只管“进来的数量”,不管“出去的速度”。如果你的协程平均耗时翻倍,而并发数没调低,排队延迟就会指数上升——这点常被忽略。
# ai
# 的是
# 就会
# 这是
# 能在
# 多个
# 翻倍
# 而非
# app
# 路由
# 循环
# 并发
# 对象
# 接口
# function
# 异步
# 死锁
# 事件
# 作用域
# Token
# try
# 闭包
# 中间件
# finally
# 信号量
# 器里
# fastapi
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Win11怎么设置快速访问主页_Windows11
- php订单日志怎么记录物流_php记录订单物流变更
- php会话怎么开启_session_start函数
- Python如何创建带属性的XML节点
- Mac怎么查看活动监视器_理解Mac进程和资源占用
- 如何在Golang中实现微服务服务拆分_Golan
- php与c语言在嵌入式中有何区别_对比两者在硬件控
- Win11怎么开启远程桌面连接_Windows11
- c# 在高并发场景下,委托和接口调用的性能对比
- Windows服务无法启动错误1067是什么_进程
- Win11怎么关闭定位服务_保护Win11位置隐私
- 如何使用Golang sort排序切片_Golan
- Windows10如何删除Windows.old_
- 如何在 IIS 上为 ASP.NET 6 应用排除
- mac怎么安装字体_MAC添加第三方字体与字体册管
- Mac版Final Cut Pro入门_Mac视频
- Windows11怎么用“记事本”自动换行与编码
- LINUX如何查看文件类型_Linux中file命
- Python文件管理规范_工程实践说明【指导】
- Windows10如何彻底关闭自动更新_Win10
- Win11开机自检怎么关闭_跳过Win11开机磁盘
- 如何使用Golang管理跨项目依赖_Golang多
- Linux如何安装JDK11_Linux环境变量配
- 如何在Golang中修改数组元素_通过指针实现原地
- 如何在Golang中处理模块冲突_解决依赖版本不兼
- Drupal 中渲染节点时出现 HTML 标签嵌套
- 如何使用Golang实现容器健康检查_监控和自动重
- 如何减少Golang内存碎片化_Golang内存分
- Win10如何卸载自带Edge_Win10彻底卸载
- Mac的访达(Finder)怎么用_Mac文件管理
- 如何在 Go 开发中正确处理本地包导入与远程模块路
- Win11任务栏天气怎么关闭 Win11隐藏天气小
- php怎么捕获异常_trycatch结构处理运行时
- PHP 中 require() 语句返回值的用法详
- Win11怎么看电池循环次数_Win11笔记本电池
- Win11怎么更改盘符_Win11磁盘管理修改驱动
- Python异步网络编程_aiohttp说明【指导
- Win11怎么调整屏幕亮度_Windows 11调
- Windows 10自带杀毒软件在哪_Window
- Linux如何申请SSL免费证书_Linux下Ce
- Win11怎么关闭粘滞键_彻底禁用Windows
- c++中explicit(bool)的用法 c++
- 如何使用Golang实现微服务状态监控_Golan
- php485函数执行慢怎么优化_php485性能提
- php8.4如何调用com组件_php8.4win
- 获取 PHP 文件最后修改时间的正确方法
- 跨文件调用类方法怎么用_php作用域操作符与自动加
- Win10电脑C盘红了怎么清理_Windows10
- php485函数怎么捕获异常_php485错误处理
- 如何在 Go 后端安全获取并验证前端存储的 JWT


QQ客服