0

0

asyncio.Semaphore 如何与限流装饰器结合使用

舞姬之光

舞姬之光

发布时间:2026-01-23 20:09:10

|

315人浏览过

|

来源于php中文网

原创

限流装饰器不能直接套 asyncio.Semaphore,因为其 acquire() 是协程需 await,而同步装饰器无法等待;正确做法是用异步装饰器封装 async with semaphore: 逻辑,确保复用同一信号量实例并自动释放。

asyncio.semaphore 如何与限流装饰器结合使用

限流装饰器为什么不能直接套 asyncio.Semaphore

因为 asyncio.Semaphoreacquire() 是协程函数,必须用 await 调用;而普通装饰器在定义时是同步执行的,无法 await 一个协程对象。直接写 @semaphore.acquire() 会报 RuntimeWarning: coroutine 'Semaphore.acquire' was never awaited,甚至导致死锁。

正确做法:用异步装饰器 + async with 包裹

核心是把信号量控制逻辑封装进一个真正的异步装饰器里,并确保每次调用都走 async with semaphore: 流程。示例如下:

import asyncio
from functools import wraps

def rate_limit(limit: int): semaphore = asyncio.Semaphore(limit) def decorator(func): @wraps(func) async def wrapper(*args, *kwargs): async with semaphore: return await func(args, **kwargs) return wrapper return decorator

@rate_limit(3) async def fetch_data(url: str): print(f"GET {url}") await asyncio.sleep(1) # 模拟请求 return f"done: {url}"

这个模式的关键点:

  • semaphore 在装饰器工厂函数中创建一次,复用同一个实例(不是每次调用都新建)
  • async with 确保自动获取/释放,即使 func 抛异常也不会漏掉 release
  • 装饰器返回的是 async def wrapper,能被 await 正确调度

常见踩坑场景与修复

实际用的时候容易掉进这几个坑:

  • 多个装饰器顺序错乱:比如同时用 @retry@rate_limit,要把 @rate_limit 放在最外层,否则重试会绕过限流
  • 信号量作用域错误:在 FastAPI 路由里误把 semaphore = asyncio.Semaphore(3) 写在 @app.get 函数内部 —— 每次请求都新建一个,完全失效
  • 跨协程共享失败:在不同模块或类方法里各自初始化 asyncio.Semaphore(3),等于建了多个独立池子,总并发数变成 3×N
  • 忘记 await 装饰后函数:调用 fetch_data("https://...") 却没加 await,结果拿到一个 coroutine 对象而非结果

进阶:按用户/路径维度做差异化限流

如果需要对不同 API 路径、不同用户 ID 或不同目标域名分别限流,就不能只用一个全局 semaphore。推荐用字典缓存 + 键隔离:

Decktopus AI
Decktopus AI

AI在线生成高质量演示文稿

下载
from collections import defaultdict
import asyncio

_semaphores = defaultdict(lambda: asyncio.Semaphore(3))

def per_domain_rate_limit(domain: str): semaphore = _semaphores[domain] def decorator(func): @wraps(func) async def wrapper(*args, *kwargs): async with semaphore: return await func(args, **kwargs) return wrapper return decorator

@per_domain_rate_limit("httpbin.org") async def fetch_httpbin(): ...

注意:_semaphores 字典本身不需要加锁 —— asyncio.Semaphore 是线程/协程安全的,但字典读写在高并发下可能有竞态,生产环境建议用 asyncio.Lock 包一层或改用 weakref.WeakValueDictionary 防内存泄漏。

真正难的不是写对语法,而是想清楚「谁和谁共用一个信号量」—— 同一资源池里的所有协程,必须共享同一个 semaphore 实例,且生命周期要覆盖整个应用运行期。

相关专题

更多
Python FastAPI异步API开发_Python怎么用FastAPI构建异步API
Python FastAPI异步API开发_Python怎么用FastAPI构建异步API

Python FastAPI 异步开发利用 async/await 关键字,通过定义异步视图函数、使用异步数据库库 (如 databases)、异步 HTTP 客户端 (如 httpx),并结合后台任务队列(如 Celery)和异步依赖项,实现高效的 I/O 密集型 API,显著提升吞吐量和响应速度,尤其适用于处理数据库查询、网络请求等耗时操作,无需阻塞主线程。

27

2025.12.22

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

482

2023.08.10

http与https有哪些区别
http与https有哪些区别

http与https的区别:1、协议安全性;2、连接方式;3、证书管理;4、连接状态;5、端口号;6、资源消耗;7、兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2033

2024.08.16

C++ 高级模板编程与元编程
C++ 高级模板编程与元编程

本专题深入讲解 C++ 中的高级模板编程与元编程技术,涵盖模板特化、SFINAE、模板递归、类型萃取、编译时常量与计算、C++17 的折叠表达式与变长模板参数等。通过多个实际示例,帮助开发者掌握 如何利用 C++ 模板机制编写高效、可扩展的通用代码,并提升代码的灵活性与性能。

9

2026.01.23

php远程文件教程合集
php远程文件教程合集

本专题整合了php远程文件相关教程,阅读专题下面的文章了解更多详细内容。

25

2026.01.22

PHP后端开发相关内容汇总
PHP后端开发相关内容汇总

本专题整合了PHP后端开发相关内容,阅读专题下面的文章了解更多详细内容。

18

2026.01.22

php会话教程合集
php会话教程合集

本专题整合了php会话教程相关合集,阅读专题下面的文章了解更多详细内容。

19

2026.01.22

宝塔PHP8.4相关教程汇总
宝塔PHP8.4相关教程汇总

本专题整合了宝塔PHP8.4相关教程,阅读专题下面的文章了解更多详细内容。

10

2026.01.22

PHP特殊符号教程合集
PHP特殊符号教程合集

本专题整合了PHP特殊符号相关处理方法,阅读专题下面的文章了解更多详细内容。

11

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Java 教程
Java 教程

共578课时 | 50.1万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.0万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号