题干与适用场景
一个异步服务在缓存命中时经常无需真正 I/O,但仍为每个协程创建任务并等待事件循环调度。团队想全局设置 asyncio.eagertaskfactory 来减少调度开销。请判断收益是否足以承担语义变化,并给出安全的灰度方案。
Python 文档说明:启用后,协程在 Task 构造期间同步开始;只有遇到阻塞才调度到事件循环。该行为从 Python 3.12 起提供,且会改变任务执行顺序。
面试官考察点
重点观察候选人是否区分“同步完成”和“异步阻塞”,能否解释 eager 执行对公平性、异常传播、取消和 TaskGroup 的影响;是否只在测量证明收益的边界启用,保留版本检测、回滚和指标。
回答前需要澄清的问题
- 服务运行的最低 Python 版本与所有部署环境是否一致?
- 协程是读本地缓存,还是可能触发网络、数据库或锁等待?
- 是否依赖任务创建顺序、事件循环公平性或
call_soon回调顺序? - 失败、取消和超时是否由
TaskGroup或请求作用域统一管理? - 优化目标是 CPU 调度开销、尾延迟,还是吞吐?基线数据是什么?
30 秒回答框架
“我不会直接全局开启。eager factory 让协程在构造 Task 时同步运行,缓存命中可省掉一次事件循环调度;遇到阻塞才排队。它改变执行顺序,可能让一个创建任务的循环长时间占用当前任务,也会改变异常何时暴露。先按 Python 版本和调用点灰度,测量命中率、事件循环延迟、尾延迟和错误率;不满足收益或破坏公平性就回退默认 factory。”
分步骤深入解答
第一步:建立默认与 eager 的执行模型
默认 create_task 会把协程安排到事件循环稍后运行;eager factory 在构造期间立即推进协程,直到返回、抛错或第一次阻塞。同步完成的任务可能根本不会进入调度队列。
loop.set_task_factory(asyncio.eager_task_factory)
task = asyncio.create_task(read_cached(key))因此,不能把 eager 当作“更快的同一调度器”;它是可观察的执行语义变化。
第二步:定位适合的协程边界
适合候选通常是纯内存缓存、记忆化计算和高命中率的短协程。会访问网络、数据库、文件、锁或无界 CPU 的协程应保持短暂、可阻塞的路径,避免在任务构造阶段抢占当前执行流。
第三步:分析顺序与公平性
多个任务在循环中创建时,eager 协程可能按创建顺序立即完成,改变原本交错的结果顺序。大量同步命中还可能让当前任务连续执行很久,推迟定时器、I/O 回调和其他请求。应监控 event-loop lag,并限制单次批量创建量。
第四步:处理异常和取消
同步抛出的异常可能在 create_task 调用附近暴露,调用栈和捕获位置与默认调度不同。调用方仍须保存 Task 引用,并让 CancelledError 继续传播。不要用 eager factory 掩盖请求取消或把异常转成成功结果。
第五步:与 TaskGroup 和超时组合
TaskGroup 仍负责任务树、兄弟取消和 join,但组内任务可能在 create_task 时已完成或失败。用结构化的 try/except* 处理异常,并在外层设置共享超时;不要假设所有任务都先排队再同时开始。
async with asyncio.TaskGroup() as group:
user = group.create_task(read_cached("user"))
orders = group.create_task(read_remote("orders"))第六步:版本与局部启用
该 API 从 Python 3.12 起提供,Python 3.14 的 createtask 还支持 eagerstart 参数。多版本服务应在启动检查版本,并优先用局部任务或独立事件循环验证,而不是无条件修改全局 factory。
第七步:设计灰度、回滚与指标
先在缓存专用调用点启用,设置可关闭开关;比较默认与 eager 的 CPU、任务创建耗时、缓存命中尾延迟、event-loop lag、异常率、取消率和下游 QPS。发现公平性或错误回归时恢复默认 factory,不需要改写任务业务逻辑。
第八步:验证语义而非只测基准
测试同步返回、同步抛错、第一次 await、外部取消、TaskGroup 多错误、超时、递归创建任务、定时器公平性和混合缓存/远程批次。用事件记录断言顺序,避免只跑吞吐基准就宣布优化成功。
高质量示范回答
“eager factory 适合高命中率、短而确定的缓存协程,因为它省去一次事件循环调度;它不适合不可预测的 I/O 或长 CPU 路径。最大风险是同步执行改变顺序、公平性和异常暴露时机。我的方案是 Python 版本检查、局部灰度、保留默认回退,并同时观测 loop lag、尾延迟、取消和错误指标。TaskGroup 与共享超时仍保留,所有取消和资源清理照旧。”
常见错误
- 把 eager 当作无语义变化的性能开关 → 顺序和异常时机改变 → 先写出两种执行模型并测量。
- 对所有协程全局启用 → 慢 I/O 或 CPU 阻塞当前任务 → 只在短同步路径灰度。
- 忽略 Python 版本 → 旧环境启动失败 → 启动时检查版本并保留默认 factory。
- 只看平均吞吐 → 事件循环公平性退化 → 加入 loop lag 和尾延迟指标。
- 认为 TaskGroup 行为完全不变 → 同步异常捕获位置错误 → 覆盖构造阶段失败和多异常测试。
- 吞掉取消或清理异常 → 请求结束后仍有泄漏 → 保留取消传播并验证资源释放。
追问及应对
追问一:缓存协程同步返回时,Task 还会存在吗?
可能在创建阶段已经完成;不要依赖它一定经历事件循环调度。调用方仍应使用返回对象读取结果,并覆盖同步完成路径。
追问二:eager 是否保证任务按创建顺序完成?
不保证业务层的全局顺序。它只改变开始时机;遇到阻塞后仍受事件循环调度,多个任务的完成顺序必须通过显式协调或结果排序保证。
追问三:如何避免一批缓存命中饿死其他请求?
限制批量大小,必要时在批次间主动让出控制权,并监控 loop lag;也可仅对单个低成本调用点使用 eager。
追问四:发生回归如何回滚?
关闭开关并恢复默认任务工厂,确认指标回到基线,再保留带事件顺序和版本信息的样本用于定位。
追问五:eager_start 与 factory 有何关系?
eager_start 是创建单个 Task 时的显式选项;factory 是事件循环级默认策略。两者都必须按实际 Python 版本确认签名和优先级,不能混用旧版本假设。