用Python 3.14跑50个asyncio任务,验证它们是不是真的用了多个线程,以及并发和串行两种写法的耗时差距。
每个任务都调用 await asyncio.sleep(0.1),分别用并发和串行两种方式跑。
代码与结果async def worker(i): await asyncio.sleep(0.1) return i # 并发:50个任务一起交给 asyncio.gather await asyncio.gather(*[worker(i) for i in range(50)]) → 耗时 0.1013s # 串行:一个一个await for i in range(50): await worker(i) → 耗时 5.0464s
结果差了将近50倍——并发跑50个"睡0.1秒"的任务,总耗时几乎还是0.1秒;串行跑,耗时变成了50×0.1=5秒。这不是多线程带来的加速,下面验证这一点。
用threading.get_ident()记录每个任务运行时所在的OS线程id。
代码与结果thread_ids = set() async def worker_tid(i): thread_ids.add(threading.get_ident()) await asyncio.sleep(0.05) thread_ids.add(threading.get_ident()) await asyncio.gather(*[worker_tid(i) for i in range(20)]) 结果: distinct OS thread ids used across 20 concurrent tasks: 1 main thread id: 8355799424
20个"并发"任务,运行时用到的OS线程id集合,大小是1——全部任务都在同一个线程里跑。事件循环做的事情是:单线程反复执行一小段一小段的代码,每当代码执行到 await 就把控制权交还给循环,循环再去看看还有哪些任务准备好继续执行了,一个一个轮流推进。这跟"多线程同时处理"完全是两回事——线程数量始终是1,"并发"来自于用等待时间去插空跑别的任务,而不是真的同时有CPU在算。
每一步都是本文上面用Python 3.14跑出来的原始结果。
asyncio.gather:并发调度多个协程threading.get_ident():验证事件循环用了几个OS线程