asyncio.Semaphore能防崩溃是因为它通过限制同时进入临界区的协程数量(如HTTP请求数),避免资源耗尽导致服务端拒绝连接或客户端内存暴涨、事件循环阻塞;必须用async with确保自动释放,且需与aiohttp连接池协同配置。

asyncio.Semaphore 为什么能防崩溃?
并发请求太多时,服务端可能直接拒绝连接或返回 503,客户端也可能因内存暴涨、事件循环阻塞而卡死。asyncio.Semaphore 不是“限速”,而是控制**同时进入临界区的协程数量**——比如同时最多 10 个 HTTP 请求在跑,其余协程会挂起等待,避免资源耗尽。
它不干预单个请求耗时,也不保证总请求数或时间窗口,只做最基础的“进门排队”:门宽固定,人多就等。
正确用法:with semaphore 括住 await 点
常见错误是把 acquire()/release() 手动拆开,或在 try/finally 里写,容易漏掉 release() 导致死锁。必须用 async with 语法,由解释器保证释放。
- ✅ 正确:
async with semaphore: async with session.get(url) as resp: return await resp.text() - ❌ 错误:
await semaphore.acquire()后没配对semaphore.release(),或放在await外层(如if判断后才 acquire) - ⚠️ 注意:
semaphore必须在所有并发协程共享同一个实例,不能每次asyncio.Semaphore(10)新建一个
和 aiohttp.ClientSession 配合的关键细节
很多人以为设了 semaphore 就万事大吉,但 aiohttp 自身也有连接池限制,两者叠加可能比预期更保守。
立即学习“Python免费学习笔记(深入)”;
-
aiohttp.TCPConnector(limit=100)控制总连接数,semaphore控制并发任务数——如果semaphore设为 50,但limit=10,实际并发仍被 connector 卡在 10 - 建议:先调低
semaphore(如 3–10),再根据错误率(ClientOSError、ServerDisconnectedError)逐步上调;同时把limit设为略大于semaphore值(如limit=semaphore._value + 5) - 超时一定要设:
timeout=aiohttp.ClientTimeout(total=30),否则一个卡死请求会永久占着信号量槽位
调试信号量是否生效的简单方法
光看代码没法确认信号量真在起作用,得靠日志或计数验证。
- 加一行日志:
print(f"[{id(asyncio.current_task())}] acquired, left: {semaphore._value}")(注意_value是内部属性,仅用于调试) - 观察并发行为:启动 100 个任务,但日志里同一时刻最多只有 N 行 “acquired”,且后续任务要等前面的
await完成才打印,说明生效 - 如果发现大量任务几乎同时打印 acquired,大概率是
semaphore实例没共享(比如在循环里重复创建)或用了同步的threading.Semaphore
信号量本身不记录谁占了槽位,也不提供排队超时机制——想加排队等待上限,得自己套一层带 asyncio.wait_for 的封装。


















