跳到主要内容

ADR-005 — 内部并发原语:Python asyncio(结构化并发 TaskGroup)

状态 = draft(formalize 架构 §12 已述倾向,待 owner 签)。里程碑归属:M0 已用 asyncio 基础;业务层 TaskGroup 并发在 M2 任务类型扩展 兑现。本 ADR 不阻塞 M1。

§0 决策简述

FinBayes Runtime 以 Python asyncio 为基础并发原语,业务对象 TaskGroup 映射 asyncio.TaskGroup(Python 3.11+ 结构化并发)。CPU 密集操作(本地推理 / 大量解析)经 loop.run_in_executor 抛线程/进程池,不阻塞事件循环。

§1 上下文(架构 §12)

  • Runtime 工作是 I/O 密集(LLM 调用 / 工具调用 / 数据 API 都是网络 I/O),非 CPU 密集。
  • 业务对象 TaskGroup(一个用户输入命中多任务时的并发容器,§11.3)需要结构化并发原语承接其失败隔离 / 取消 / 超时语义。

§2 决策与备选

方案评价
✅ asyncio + TaskGroup单线程事件循环避免锁竞争/死锁;与 httpx/aiohttp/FastAPI 生态契合;3.11+ 结构化并发匹配业务 TaskGroup(父任务统一管理子任务生命周期/取消)
❌ 多线程I/O 场景被 GIL 限制收益有限;锁/死锁坑多;失败隔离与取消语义比 TaskGroup 难表达
❌ 多进程IPC + 序列化开销重;单机认知层 MVP 不需要真并行 CPU;过度工程

例外:CPU 密集(本地 LLM 推理 / 大量 PDF 解析)→ run_in_executor 到线程池/进程池。

§3 后果

  • 业务 TaskGroup → asyncio.TaskGroup:子任务失败隔离(一个失败不阻塞其他)、用户取消向子任务传播 cancel、超时经 asyncio.timeout
  • 阻塞调用必须包 executor,否则卡事件循环(§12 测试约束需有断言)。
  • 测试用 pytest-asyncio / anyio。

§4 里程碑

  • M0:asyncio 基础(asyncio.TaskGroup 基础)已在骨架。
  • M2:业务 TaskGroup 多任务并行 + 失败隔离 + 取消/超时语义完整落地(本 ADR 的主要兑现点)。

§5 关联