You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何CPU密集型任务未阻塞Python Asyncio事件循环?

为何CPU密集型任务未阻塞Python Asyncio事件循环?

嘿,这个问题其实是Asyncio事件循环调度逻辑的典型坑,我来给你一步步拆解~

首先看你第一个实验的执行流程:

  • 你先await io_bound(1),这个任务会完整执行完,耗时1秒到17:28:21。
  • 接着调用asyncio.create_task(cpu_bound())——注意,这只是把CPU任务加入事件循环的待执行队列,但事件循环此时还在main函数的执行流里,根本没机会切换到这个CPU任务。
  • 紧接着你就await io_bound(2):这个任务先打印启动日志,然后执行await asyncio.sleep(1)。这时候,事件循环终于有机会暂停当前的io_bound(2)任务,去调度队列里的其他任务——也就是刚加入的cpu_bound()。

但这里的关键来了:cpu_bound()是纯同步的CPU密集型代码,里面没有任何await语句。Asyncio的事件循环是单线程的,只有当任务遇到await(或者其他可暂停的点)时,才会让出CPU给其他任务。而你的cpu_bound()一旦开始执行,就会霸占整个线程,直到它跑完那5亿次循环(耗时约5秒),中间完全不会给事件循环任何调度其他任务的机会。

那为什么io_bound(2)会和cpu_bound()同时结束?其实是因为:

  • io_bound(2)的asyncio.sleep(1)在17:28:22就到期了,但此时事件循环被cpu_bound()死死占着,根本没法切换回io_bound(2)任务去执行后续的打印操作。
  • 直到17:28:26cpu_bound()跑完,事件循环才终于解脱,立刻回到io_bound(2)任务,发现sleep已经到期,于是马上打印结束日志——所以看起来两者同时完成。

你的预期其实是对的:CPU密集型任务确实会阻塞事件循环,但第一个实验的执行顺序让你产生了误解。

后来你加入await asyncio.sleep(0.0001)的修改,正好命中了关键:
在创建cpu_bound()任务后,你加了一个极短的sleep,这会让事件循环立刻暂停main函数,优先执行队列里的cpu_bound()任务。这时候cpu_bound()霸占线程,直到它跑完,事件循环才回到main函数,继续执行await io_bound(2)——这时候io_bound(2)才真正开始执行,sleep1.5秒后完成,完全符合你预期的阻塞效果。

总结一下:

  • Asyncio的单线程特性决定了,任何没有await的同步代码(比如你的CPU密集型循环)都会阻塞整个事件循环。
  • 任务调度的时机很重要:只有当当前任务遇到await时,事件循环才会去处理队列里的其他任务。

备注:内容来源于stack exchange,提问作者Philipp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 09:09:30