Python两套原生信号量与swift对比
Python两套原生信号量,正好一一对应iOS两套
Python标准库就两套信号量,名字一模一样,但是完全不能混用。
threading.Semaphore→ 完全等价 iOS GCDDispatchSemaphore
操作对象:操作系统内核线程
sem.acquire():拿不到许可,直接把当前OS线程阻塞休眠,线程原地卡住,不归还;别的线程调用release()才唤醒这条线程。
✅必须多操作系统线程才有意义;单线程调用会卡死死锁。
搭配:threading.Thread/ThreadPoolExecutor(同步多线程)。
asyncio.Semaphore→ 完全等价 iOS15+ Swift ConcurrencyAsyncSemaphore
操作对象:协程Task(用户态对象),不碰内核线程
await sem.acquire():拿不到许可,挂起协程,把当前占用的OS线程还给事件循环,线程去跑别的协程;等待期间不占线程。
✅跑在单OS线程事件循环,不需要开多内核线程。
搭配:async def协程、aiohttp,就是MinerU MCP用的那套。
补充还有
BoundedSemaphore,分别在threading和asyncio下面,只是防止过度release,底层行为不变。
一一映射对照表
| Python | iOS | 行为 | 是否阻塞OS内核线程 |
|---|---|---|---|
threading.Semaphore |
DispatchSemaphore(GCD) |
线程信号量 | ✅会阻塞休眠OS线程 |
asyncio.Semaphore |
AsyncSemaphore(Swift Concurrency) |
协程Task信号量 | ❌只挂起Task,释放线程 |
重点坑(和iOS一模一样)
- ❌千万不要在async协程里面用
threading.Semaphore
就像Swift Task里面不要调用DispatchSemaphore.wait()。
# 灾难写法!
async def bad():
sem = threading.Semaphore(0)
sem.acquire() # 直接把事件循环那一条OS主线程阻塞卡死,全部协程冻住
threading.Semaphore.acquire()是同步阻塞调用,没有await,直接卡住内核线程,整个asyncio事件循环卡死。
- ❌
asyncio.Semaphore线程不安全,不能跨不同操作系统线程使用,只能在同一个事件循环内部协程之间用,这点也和Swift的AsyncSemaphore一致。
结合MinerU的场景复盘
- 如果你写同步requests + ThreadPoolExecutor(max_workers=3):
- 底层是多条OS线程;
- 限流优先用
max_workers,也可以叠加threading.Semaphore,两者都是操作OS线程。
- 如果你写aiohttp异步协程(第三方MinerU‑MCP):
- 单OS线程,大量协程对象;
- 用
asyncio.Semaphore(2‑4)做闸门,协程挂起、线程归还,不会线程爆炸。
python也有有类似于iOS GCD全局队列那种信号量
就是threading.Semaphore,就是GCD DispatchSemaphore的Python复刻,作用于操作系统线程,acquire会把线程阻塞休眠,必须多线程才能干活。
极简示例,GCD等价的Python threading.Semaphore
from threading import Thread, Semaphore
import time
sem = Semaphore(2) # 最多2个并发,等价DispatchSemaphore(value:2)
def worker(i):
sem.acquire() # 许可耗尽,当前OS线程直接阻塞休眠
print(f"线程{i}运行")
time.sleep(3)
sem.release()
for i in range(10):
Thread(target=worker, args=(i,)).start()
现象:会创建多条操作系统线程;拿不到许可的线程直接内核休眠,GCD那种线程爆炸的问题,Python这里同样会出现。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)