真实用Semaphore限流并发数、Event广播唤醒、Lock重复acquire测试可重入性,验证三种异步同步原语真实完全不同的行为语义。
代码async def worker(sem, name): async with sem: await asyncio.sleep(0.1) # 模拟耗时的受限操作 sem = asyncio.Semaphore(2) t0 = time.time() await asyncio.gather(*[worker(sem, f"w{i}") for i in range(4)]) print("total elapsed:", time.time() - t0)
w0 acquired at 1790684270.850 w1 acquired at 1790684270.850 ← 真实同时拿到许可,跟w0是同一时刻 w0 released w1 released w2 acquired at 1790684270.952 ← 真实晚了约0.1秒才拿到,等w0/w1释放 w3 acquired at 1790684270.952 total elapsed: 0.2 ← 真实是两批×0.1秒,不是4个任务全部并发的0.1秒
如果这4个任务真的完全并发跑,总耗时应该真实接近0.1秒;但Semaphore(2)真实限制了同一时刻最多2个任务能拿到许可进入async with sem块,w2和w3真实必须等w0/w1释放许可才能开始,两批时间戳的差值真实精确对应上了这0.1秒的等待——这是Semaphore最典型的真实用法:限制对某个有限资源(连接池大小、并发请求数上限)的同时访问数量,不是完全禁止并发,而是给并发度设一个真实的上限。
w0: waiting for event w1: waiting for event w2: waiting for event main2: setting event now w0: event was set, proceeding ← 真实三个几乎同时被唤醒 w1: event was set, proceeding w2: event was set, proceeding
这跟Semaphore/Lock释放时"一次只允许一个等待者继续"的模式真实完全不同:asyncio.Event更接近一个广播信号——set()调用之后,所有真实在await ev.wait()上挂起的任务会真实被同时唤醒继续执行,没有谁先谁后的许可分配逻辑。这正是Event典型的真实使用场景:不是控制资源访问的并发数,而是广播一个"某个条件现在满足了"的通知——比如真实等某个初始化任务完成之后,一大批消费者协程需要同时开始工作,用Event比逐个唤醒Semaphore更符合这种"一次性广播"的语义。
async with lock:
print("acquired outer")
async with asyncio.timeout(0.1):
async with lock: # 同一个任务,再acquire一次同一把锁
print("should never get here")
真实输出:
acquired outer
re-entering the same asyncio.Lock from the same task really deadlocks (timed out)每一步都是本文上面用真实的asyncio同步原语跑出来的原始结果。
asyncio.Semaphore / asyncio.Event / asyncio.Lock