必须用 async with 或显式 await acquire/release 才能生效,信号量需全局唯一复用且初始化值即硬性上限,否则并发限制失效。
用 await 配合 asyncio.Semaphore 实现硬性并发限制,核心在于:信号量必须在协程内部、每次任务真正执行前被 await 获取,且必须通过 async with 或显式 await semaphore.acquire() + semaphore.release() 保证释放。这不是“建议”,而是硬性管控生效的前提。
信号量不是装饰器,也不是全局开关;它只在你 await 它的时候才起作用。手动调用 acquire() 而不 await,或漏掉 release(),都会导致计数错乱、任务永久阻塞或并发失控。
async with semaphore: —— 自动 await acquire() 并在退出时自动 release(),异常安全await semaphore.acquire() 后必须配对 semaphore.release(),且要放在 try/finally 中semaphore.acquire()(没 await)→ 返回一个协程对象,但没执行,信号量计数不变await semaphore.acquire() 后没 release() → 计数持续减少,后续所有任务卡死asyncio.Semaphore(N) 的 N 就是硬性上限,事件循环不会绕过它。哪怕你同时 gather 1000 个任务,也只会让其中最多 N 个进入 async with 块开始执行,其余全部挂起等待——这是 asyncio 内置的调度行为,无需额外逻辑。
semaphore = asyncio.Semaphore(5) → 任何时候最多 5 个协程处于“已获取信号量 + 执行中”状态time.sleep 或计数器模拟限流——那只是软性节流,无法阻止并发资源争抢每个需要受控的并发场景,必须共用同一个 semaphore 实例。如果每次调用都新建 asyncio.Semaphore(5),那就等于放开了限制——因为每个新信号量都是独立计数器,互不影响。
若担心开发中误调多次 release() 导致计数溢出(比如本该释放 1 次却写了 2 次),可用 asyncio.BoundedSemaphore(N) 替代。它会在 release() 时检查当前值是否超过初始 N,超限则抛出 ValueError,强制暴露错误。
Semaphore 几乎无差别