Python中threading.Semaphore与asyncio.Semaphore的差异及使用疑问
threading.Semaphore 与 asyncio.Semaphore 的核心差异
- 调度逻辑不同:
threading.Semaphore面向多OS线程场景设计,acquire()拿不到信号量时会阻塞当前整个OS线程,由内核调度其他线程运行。asyncio.Semaphore面向单线程协程场景设计,拿不到信号量时只会挂起当前协程,不会阻塞所在的OS线程,事件循环可继续调度其他就绪协程。 - 底层实现不同:
threading.Semaphore依赖内核提供的同步原语(如Linux下的futex)实现,属于内核态同步机制。asyncio.Semaphore完全是用户态实现,基于asyncio事件循环的协程挂起/唤醒逻辑运作,无内核态阻塞调用。 - 适用范围不同:前者用于多OS线程间的资源并发限制,后者仅适用于同个事件循环内的协程间的资源并发限制。
异步函数中使用 threading.Semaphore 的风险
会直接导致异步事件循环卡死,完全失去异步性能优势。
原因很简单:threading.Semaphore.acquire() 是阻塞调用,只要拿不到信号量就会堵死当前整个OS线程,而asyncio的事件循环默认运行在单线程上,线程被阻塞后所有待调度的协程都会停止运行,哪怕是和这个信号量完全无关的异步任务也无法执行。
举个实际场景:如果你的信号量上限是5,同时有100个协程发起acquire请求,前5个拿到信号量之后,第6个协程调用acquire就会直接堵死整个事件循环,剩下的94个协程无论是否需要用到这个信号量,都无法被调度,直到阻塞的acquire调用返回为止。
官方“asyncio原语不是线程安全”说明的含义
官方原文如下:
asyncio primitives are not thread-safe, therefore they should not be used for OS thread synchronization (use threading for that)
这句话包含两层明确的要求:
- asyncio的所有同步原语(包括Semaphore、Lock、Event、Condition等)内部没有做任何多线程并发访问的防护,如果你在多个OS线程中同时操作同一个asyncio原语实例,会触发竞态条件,出现计数异常、漏唤醒、死锁等不可预期的逻辑错误。
- asyncio同步原语的设计目标就不是解决OS线程之间的同步问题,它的作用域仅限于同一个OS线程下、归属同一个事件循环的协程之间的同步。如果你需要实现多个OS线程之间的同步,必须使用
threading模块下的对应同步原语,不要用asyncio的。
常见的错误用法示例:在A线程的事件循环中创建asyncio.Semaphore实例,然后传给B线程中的逻辑调用acquire/release,这种写法几乎一定会出现难以排查的bug。
内容的提问来源于stack exchange,提问作者user8942442
相关产品推荐
相关产品推荐

