bigywater.tech
Python · 1

asyncio 事件循环:一个线程怎么同时处理很多任务

用Python 3.14跑50个asyncio任务,验证它们是不是真的用了多个线程,以及并发和串行两种写法的耗时差距。

50倍
并发 vs 串行的耗时差距
1个线程
50个并发任务用到的OS线程数
0.1s vs 5.0s
同样50个任务,两种写法的耗时

做的事:50个"睡0.1秒"的任务,只用1个线程

每个任务都调用 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秒。这不是多线程带来的加速,下面验证这一点。

验证:这50个并发任务,全部挤在同一个线程里

用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在算。

这也是为什么asyncio特别适合"要等很多东西"的场景(网络请求、文件IO、数据库查询):等待的这段时间里CPU本来就闲着,事件循环做的就是把这些空闲时间见缝插针地分给其他任务,一个线程就能同时"管"住成百上千个在等待的任务。

分步演示:单线程是怎么做到"并发"的

每一步都是本文上面用Python 3.14跑出来的原始结果。

一个线程,是怎么同时管住50个等待中的任务的
这一步新验证的结果

    本文用到的工具

    • 本机 Python 3.14.7
    • asyncio.gather:并发调度多个协程
    • threading.get_ident():验证事件循环用了几个OS线程
    京ICP备2026067502号-1京公网安备11010502063487号