asyncio.Semaphore 如何与限流装饰器结合限并发
应使用 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)
@with_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 注入带信号量的依赖,语义更正、测试更方便:
标小兔AI写标书
一款专业的标书AI代写平台,提供专业AI标书代写服务,安全、稳定、速度快,可满足各类招投标需求,标小兔,写标书,快如兔。
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。它不是语法糖,是安全边界。
信号量不是魔法,它只管“进来的数量”,不管“出去的速度”。如果你的协程平均耗时翻倍,而并发数没调低,排队延迟就会指数上升——这点常被忽略。
如非特殊说明,本站内容均来自于网友自主分享,如有任何问题均请联系我们进行处理!
猜您喜欢
MiMo Code:AI 自动编码如何辅助构建高并发缓存逻辑
2026-06-28MiMoCode是能理解项目上下文、拆解工程目标并协同执行多阶段任务的终端编码代理,擅长高并发缓存等系统级任务,具备上下文感知、Compose模式驱动、持久记忆、安全编辑与静态分析能力。 ☞☞☞AI智能聊天,问答助手,AI智能搜索,多模态理解力帮你轻松跨越从0到1的创作门槛☜☜☜ MiMoCode不是简单补几行代码的助手,而是能理解项目上下文、拆解工程目标、协同执行多阶段任务的终端编码代理。构建高...
我的世界方块宝可梦装饰方块合成表大全
2026-06-27我的世界里面有很多的mod,模组中所有内容的合成都是不一样的,今天给大家带来了游戏里面的方块宝可梦模组装饰方块合成图鉴分享,还有小伙伴不清楚,那么下面就是具体的内容. 我的世界方块宝可梦装饰方块合成表大全 球果木 合成表: 本文内容来源于互联网,如有侵权请联系删除。
微信新功能官宣 合并发图更便利 聊天花样也多了
2026-06-13今天微信派公众号发文宣布更新实用功能,微信聊天现已支持多图发送后合并展示。 据介绍,当用户一次发送3张及以上图片、视频时,就能看到“发送后合并展示”选项。 发送后,多张图片或视频会以合并形式呈现在聊天窗口中,用户可通过点击进行展开或收起,快速切换展示形态。 同时,合并展示的内容还支持一键全部保存,也可以直接转发给其他朋友。 发图的方式变了,聊天的花样也更多了,无论是研究穿搭、分享旅行照片,还是用表...
如何修复Python aiohttp请求时出现的ClientPayloadError?
2026-06-04ClientPayloadError表明服务端提前关闭连接,非客户端代码错误;常见于服务端超时、拒绝大响应或代理截断,需通过抓包、检查响应头、服务端日志及添加读取超时和异常处理来定位解决。 ClientPayloadError通常意味着服务端提前关闭了连接 这个错误不是客户端代码写错了,而是aiohttp在读取响应体时发现服务端已经断开了连接。常见于服务端超时、主动拒绝大响应、或代理/网关(如Ng...
如何使用Python监控服务器CPU温度并邮件告警_通过psutil模块读取系统信息
2026-06-04psutil无法直接读取CPU温度,因其依赖系统硬件接口且Linux需lm_sensors支持、Windows/macOS完全不支持;可靠方式是用subprocess调用sensors命令解析输出,并配合邮件告警与健壮性设计。 psutil本身不提供CPU温度读取功能——它无法直接获取温度传感器数据,强行调用psutil.sensors_temperatures()在多数Linux发行版(尤其是无...
如何在Python中验证文件的MD5校验和以确保数据完整性?
2026-06-04必须分块读取,推荐8192或65536字节块大小,以rb模式打开文件,循环调用update(),最后用hexdigest()获取32位小写十六进制字符串;校验时需strip().lower()统一格式并做长度校验。 用hashlib.md5()读取文件并计算校验和 直接调用hashlib.md5()并传入整个文件内容(比如md5.update(f.read()))在大文件上会爆内存。必须分块读取,...
怎样在Python Tkinter中使用线程池处理高并发的GUI数据更新?
2026-06-04不能直接用ThreadPoolExecutor更新Tkinter控件,因为Tkinter的Tcl解释器只允许主线程操作UI;子线程(包括线程池中的工作线程)调用widget方法会引发TclError或卡死;必须通过queue.Queue+root.after()将结果安全传回主线程更新。 为什么不能直接用concurrent.futures.ThreadPoolExecutor更新Tkinter控...
如何在Python 3中正确判断一个对象是否为某个类的实例?
2026-06-04应优先使用isinstance()而非type()做类型判断,因isinstance()遵循MRO、支持继承与ABC,体现鸭子类型哲学;type()仅判别直接类型,适用极少数需严格身份校验的场景。 用isinstance()而不是type() 直接比较type(obj)==SomeClass在绝大多数场景下是错的——它无法识别子类实例。比如isinstance(42,int)返回True,但typ...
为什么Python Django默认使用PBKDF2算法进行密码哈希?
2026-06-04Django默认使用PBKDF2而非Argon2或bcrypt,因其零依赖、NIST合规、迭代与salt可扩展,且默认26万次迭代兼顾安全与性能;make_password和check_password均动态读取PASSWORD_HASHERS[0],支持渐进式算法迁移。 PBKDF2是Django默认密码哈希算法,因为它在安全性、兼容性与可配置性之间取得了最务实的平衡。 为什么不是更“新”的Ar...
如何在Python中自定义NumPy的打印格式以方便调试?
2026-06-04np.set_printoptions()可直接控制NumPy数组显示格式;默认格式因小数位不一致、科学计数法混用及自动截断而影响调试效率;关键参数需协同设置,如precision控制有效位数、suppress禁用科学计数法、threshold避免省略、linewidth防换行混乱、floatmode='fixed'统一小数位;调试时应按目标切换组合,并注意全局生效与及时还原。 直接用np.set...
