Python `asyncio` 与 C++ Fiber 的原理与逻辑分析

1.异同点

Python asyncio 和 C++ fiber 都属于协作式并发模型:任务不会被操作系统强制抢占,而是在遇到某些"让出执行权"的点时主动暂停,让调度器运行其他任务。

但二者的抽象层级不同:

维度 Python asyncio C++ Fiber
核心抽象 coroutineTaskFuture、event loop fiber、scheduler、上下文切换
调度方式 event loop 调度 coroutine scheduler 调度 fiber
让出点 await yield / 阻塞点被 fiber runtime 接管
栈模型 通常是无栈协程 stackless coroutine 通常是有栈协程 stackful coroutine
典型用途 高并发 I/O、网络服务、异步任务编排 高性能服务、游戏服务器、用户态线程、复杂控制流
语言/库关系 Python 标准库内置运行时 C++ 无统一标准 fiber runtime,常依赖库

一句话概括:

asyncio 是围绕事件循环构建的异步 I/O 协程框架;C++ fiber 更像用户态轻量线程,由运行时调度并进行用户态上下文切换。


2. Python asyncio 的原理

2.1 基本组成

Python asyncio 主要由以下几个部分组成:

text 复制代码
coroutine  ->  Task  ->  event loop  ->  selector / proactor / I/O backend

常见对象包括:

概念 作用
async def 定义 coroutine function
coroutine object 调用 async def 后得到的可等待对象
await 挂起当前 coroutine,等待另一个 awaitable 完成
asyncio.Task 把 coroutine 包装成可被 event loop 调度的任务
asyncio.Future 表示一个未来会完成的结果
event loop 调度任务、监听 I/O、处理 timer、回调等
asyncio.gather() 并发等待多个 awaitable

2.2 coroutine 的执行逻辑

示例:

python 复制代码
import asyncio

async def fetch():
    print("start")
    await asyncio.sleep(1)
    print("end")
    return 42

asyncio.run(fetch())

执行逻辑大致如下:

text 复制代码
1. 调用 fetch(),不会立即执行函数体,而是返回 coroutine object
2. asyncio.run() 创建 event loop
3. event loop 把 coroutine 包装成 Task
4. Task 开始执行 coroutine
5. 遇到 await asyncio.sleep(1)
6. 当前 coroutine 挂起,控制权返回 event loop
7. event loop 可以运行其他 Task
8. 1 秒后 timer 到期,event loop 恢复该 coroutine
9. coroutine 继续执行并返回结果

关键点:

text 复制代码
await 不是阻塞线程,而是挂起当前 coroutine。

2.3 event loop 的角色

event loop 是 asyncio 的调度中心。

它大致做这些事情:

text 复制代码
while True:
    1. 执行 ready queue 中可运行的回调和 Task
    2. 检查 timer 是否到期
    3. 等待 I/O 事件
    4. I/O 就绪后,把对应 Task 放回 ready queue
    5. 重复

可以理解为:

text 复制代码
event loop = cooperative scheduler + I/O multiplexer + timer manager

在 Linux 上,底层常见机制是 epoll;在 macOS/BSD 上常见是 kqueue;在 Windows 上可能是 IOCP 或 selector 机制。开发者一般不直接操作这些底层接口,而是通过 asyncio 抽象使用。


2.4 await 的本质

await 的作用是:

text 复制代码
当前 coroutine 暂停执行,把控制权交还给 event loop。

逻辑上类似:

text 复制代码
result = await something

可拆成:

text 复制代码
1. 当前 coroutine 依赖 something 的完成结果
2. 当前 coroutine 暂停
3. event loop 继续执行其他任务
4. something 完成后,当前 coroutine 被重新调度
5. await 表达式返回结果

所以:

text 复制代码
await = 协作式让出控制权 + 等待结果

2.5 asyncio.gather() 的逻辑

python 复制代码
results = await asyncio.gather(task1(), task2(), task3())

含义是:

text 复制代码
1. 把多个 coroutine / Task 注册到 event loop
2. 让它们并发推进
3. 当前 coroutine 等待所有任务完成
4. 返回所有任务的结果列表

等价于 C++ 生态中常说的:

text 复制代码
when_all(task1, task2, task3)

但需要注意:

text 复制代码
asyncio.gather() 不是多线程并行。

默认情况下,它们仍然运行在同一个线程的同一个 event loop 上。并发来自于 I/O 等待期间的任务切换,而不是 CPU 并行。


3. C++ Fiber 的原理

3.1 Fiber 是什么

Fiber 通常被称为:

text 复制代码
用户态线程
轻量线程
有栈协程
stackful coroutine

它和 OS thread 的区别是:

维度 OS Thread Fiber
调度者 操作系统内核 用户态 scheduler
切换成本 较高,需要内核参与 较低,通常用户态完成
每个线程有自己的栈 每个 fiber 通常也有自己的栈
抢占 通常可抢占 通常协作式
并行性 多核并行 单线程内不并行,多线程调度器可并行

Fiber 的核心思想:

text 复制代码
把"执行上下文"保存起来,在用户态切换到另一个执行上下文。

一个 fiber 一般包含:

text 复制代码
1. 独立栈空间
2. 寄存器上下文
3. instruction pointer / program counter
4. 状态信息
5. 调度器相关元数据

3.2 Fiber 的执行逻辑

伪代码:

cpp 复制代码
void fiber_func() {
    std::cout << "A\n";
    fiber_yield();
    std::cout << "B\n";
}

执行逻辑:

text 复制代码
1. scheduler 创建 fiber,并分配栈
2. fiber 开始执行 fiber_func
3. 输出 A
4. 调用 fiber_yield()
5. 保存当前 fiber 的寄存器、栈指针、指令位置
6. scheduler 选择另一个 fiber 执行
7. 之后某个时刻,scheduler 恢复这个 fiber
8. fiber 从 yield 后继续执行
9. 输出 B

关键点:

text 复制代码
fiber_yield() 不是退出函数,而是暂停整个调用栈。

3.3 Stackful 与 Stackless

这是理解 asyncio 与 fiber 差异的关键。

Python asyncio: 通常是 stackless

asyncio coroutine 是无栈协程。

这意味着:

text 复制代码
只有 coroutine 自己的状态机会被保存。
普通函数调用栈不会整体挂起。

示例:

python 复制代码
async def a():
    await b()

async def b():
    await c()

只有 async 函数链路可以被 await 挂起。普通同步函数内部不能直接 await

例如:

python 复制代码
def normal_func():
    await something  # 语法错误

因为普通函数不是 coroutine,无法被 event loop 挂起和恢复。


C++ Fiber: 通常是 stackful

Fiber 是有栈协程。

它可以在深层普通函数调用中 yield:

cpp 复制代码
void deep_function() {
    fiber_yield();
}

void middle_function() {
    deep_function();
}

void fiber_main() {
    middle_function();
}

deep_function() 调用 fiber_yield() 时,整个调用栈都会被暂停。

这意味着:

text 复制代码
fiber 可以挂起整个调用栈;
asyncio coroutine 通常只能在 async/await 链路上挂起。

3.4 Fiber scheduler

Fiber 需要 scheduler 来决定:

text 复制代码
1. 哪个 fiber 当前可运行
2. 当前 fiber yield 后切换到哪个 fiber
3. I/O 未就绪时如何挂起 fiber
4. I/O 就绪时如何恢复 fiber
5. 是否支持多个 OS thread 上的 work stealing

一个简单 scheduler 的逻辑:

text 复制代码
ready_queue = []

while not ready_queue.empty():
    fiber = ready_queue.pop()
    switch_to(fiber)

    if fiber.finished:
        destroy(fiber)
    elif fiber.waiting:
        put_to_wait_list(fiber)
    else:
        ready_queue.push(fiber)

如果要支持异步 I/O,scheduler 还需要结合:

text 复制代码
epoll / kqueue / IOCP
timer
mutex / condition variable
network runtime

4. asyncio 与 C++ Fiber 的核心映射

Python asyncio C++ Fiber 生态中的近似概念
coroutine coroutine / fiber task
asyncio.Task fiber / scheduled task
event loop scheduler / reactor / executor
await yield / suspend / wait
asyncio.gather() when_all
asyncio.Future future / promise
ready queue runnable fiber queue
callback continuation
cancellation cancellation token / task cancellation
asyncio.sleep() timer-based suspend
async I/O non-blocking I/O + scheduler integration

5. 调度模型对比

5.1 asyncio 调度

asyncio 的调度单位是 coroutine wrapped by Task。

text 复制代码
Task A runs
  -> await I/O
  -> Task A suspended
event loop runs Task B
  -> await timer
  -> Task B suspended
I/O ready
  -> Task A resumes
timer ready
  -> Task B resumes

特点:

text 复制代码
1. 单线程事件循环为主
2. I/O 驱动
3. await 是明确的切换点
4. 不会在任意 Python 字节码之间切换
5. 适合大量 I/O-bound 任务

5.2 Fiber 调度

Fiber 的调度单位是 fiber。

text 复制代码
Fiber A runs
  -> yield / wait socket
  -> scheduler switches to Fiber B
Fiber B runs
  -> yield / wait timer
  -> scheduler switches to Fiber C
socket ready
  -> scheduler resumes Fiber A

特点:

text 复制代码
1. 可在用户态切换完整调用栈
2. 调度器由库或框架实现
3. 可以做得像同步代码一样
4. I/O 阻塞点通常需要 runtime hook
5. 适合复杂控制流和高性能服务

6. 挂起与恢复机制

6.1 asyncio 的挂起

asyncio 挂起 coroutine 时,保存的是:

text 复制代码
1. coroutine 的状态
2. 当前执行位置
3. 局部变量
4. 等待的 Future / Task

不保存完整 C 调用栈。

所以它更像:

text 复制代码
状态机

例如:

python 复制代码
async def f():
    x = 1
    await g()
    y = 2

可粗略理解为被转换成:

text 复制代码
state 0:
    x = 1
    wait g()
    state = 1
    return to event loop

state 1:
    y = 2
    finish

6.2 Fiber 的挂起

Fiber 挂起时,保存的是:

text 复制代码
1. 栈指针
2. 指令指针
3. 寄存器
4. fiber 栈内容

恢复时:

text 复制代码
1. 恢复栈指针
2. 恢复寄存器
3. 跳回之前暂停的位置
4. 继续运行

因此 fiber 更像:

text 复制代码
轻量级线程上下文切换

7. I/O 模型差异

7.1 asyncio I/O

asyncio 强依赖非阻塞 I/O。

例如:

python 复制代码
reader, writer = await asyncio.open_connection("example.com", 80)

背后逻辑:

text 复制代码
1. socket 设置为 non-blocking
2. 发起 connect/read/write
3. 如果不能立即完成,注册到 event loop
4. event loop 监听 fd 是否就绪
5. 就绪后恢复对应 Task

如果在 asyncio 中调用普通阻塞函数:

python 复制代码
time.sleep(10)

会阻塞整个 event loop。

正确做法是:

python 复制代码
await asyncio.sleep(10)

或者把阻塞任务放入线程池:

python 复制代码
await asyncio.to_thread(blocking_func)

7.2 Fiber I/O

Fiber 生态中常见两种方式:

方式一:显式异步 I/O
text 复制代码
fiber 调用 async_read
如果未就绪,则 yield
I/O 完成后 scheduler 恢复 fiber
方式二:hook 阻塞 I/O

某些 fiber runtime 会 hook 常见阻塞系统调用,例如:

text 复制代码
read
write
connect
sleep
mutex lock

让开发者写同步风格代码:

cpp 复制代码
auto data = socket.read();

但底层实际逻辑是:

text 复制代码
1. socket 未就绪
2. 当前 fiber 挂起
3. scheduler 运行其他 fiber
4. socket 就绪
5. 当前 fiber 恢复

这也是 fiber 很有吸引力的地方:

text 复制代码
代码看起来同步,运行时行为却是异步协作调度。

8. 错误传播与取消

8.1 asyncio

asyncio 中异常会沿 await 传播:

python 复制代码
async def child():
    raise RuntimeError("error")

async def main():
    await child()

main() 会收到 RuntimeError

gather() 的错误行为需要特别注意:

python 复制代码
await asyncio.gather(a(), b(), c())

默认情况下,如果其中一个 awaitable 抛异常,gather() 会向调用方传播该异常。其他任务的处理行为取决于具体版本和调用方式,需要根据语义谨慎设计。

取消任务:

python 复制代码
task.cancel()

会向 coroutine 注入 CancelledError


8.2 C++ Fiber

C++ fiber 的错误传播取决于具体库。

常见方式包括:

text 复制代码
1. exception_ptr 保存异常
2. future/promise 传播异常
3. scheduler 捕获顶层异常
4. task join 时重新抛出异常

取消通常依赖:

text 复制代码
1. cancellation token
2. stop_token
3. 自定义标志位
4. runtime-specific cancellation

C++ 本身没有统一的 fiber 取消语义。


9. 并发与并行

9.1 asyncio

asyncio 默认是单线程并发:

text 复制代码
多个 Task 交替运行,但同一时刻只有一个 Task 在 Python 线程中执行。

适合:

text 复制代码
1. 网络 I/O
2. 文件/数据库异步驱动
3. WebSocket
4. HTTP 客户端/服务端
5. 大量等待型任务

不适合直接处理大量 CPU-bound 计算。

CPU-bound 场景需要:

text 复制代码
1. multiprocessing
2. ProcessPoolExecutor
3. C 扩展释放 GIL
4. 外部计算服务

9.2 Fiber

Fiber 在单个 OS thread 上也是并发,不是并行。

但 C++ fiber runtime 可以设计成:

text 复制代码
多个 OS thread
每个 thread 一个 scheduler
fiber 在 thread 间迁移
work stealing

这时可以实现:

text 复制代码
M:N 调度模型

即:

text 复制代码
M 个 fiber 映射到 N 个 OS thread

这样可以同时获得:

text 复制代码
1. 用户态轻量调度
2. 多核并行能力

10. 性能特点

10.1 asyncio

优点:

text 复制代码
1. Python 标准库内置
2. 生态成熟
3. 非常适合 I/O-bound 高并发
4. 任务创建成本低于线程
5. 编程模型清晰,await 点显式

缺点:

text 复制代码
1. 单线程 event loop 容易被阻塞
2. CPU-bound 任务受 GIL 影响
3. async/await 会污染调用链
4. 所有阻塞库都需要 async 版本或放入线程池
5. 调试复杂并发时需要理解 Task 状态

10.2 C++ Fiber

优点:

text 复制代码
1. 切换成本低于 OS thread
2. 可保留同步代码风格
3. 有栈模型适合复杂调用链
4. 可构建高性能网络运行时
5. 可以与多线程结合实现 M:N 调度

缺点:

text 复制代码
1. C++ 标准库没有统一 fiber
2. 依赖具体 runtime 或第三方库
3. 栈大小、生命周期、调度策略需要谨慎管理
4. 与阻塞系统调用集成复杂
5. 调试上下文切换和竞态问题可能困难

11. 典型代码风格对比

11.1 Python asyncio

python 复制代码
import asyncio

async def worker(name, delay):
    print(f"{name} start")
    await asyncio.sleep(delay)
    print(f"{name} done")
    return name

async def main():
    results = await asyncio.gather(
        worker("A", 1),
        worker("B", 2),
        worker("C", 3),
    )
    print(results)

asyncio.run(main())

逻辑:

text 复制代码
main coroutine
  -> gather creates/schedules workers
  -> workers hit await sleep
  -> event loop handles timers
  -> all workers complete
  -> gather returns results

11.2 C++ Fiber 风格伪代码

不同 fiber 库 API 不统一,以下是抽象伪代码:

cpp 复制代码
void worker(std::string name, int delay) {
    std::cout << name << " start\n";
    fiber_sleep(delay);
    std::cout << name << " done\n";
}

int main() {
    Scheduler scheduler;

    scheduler.spawn([] { worker("A", 1); });
    scheduler.spawn([] { worker("B", 2); });
    scheduler.spawn([] { worker("C", 3); });

    scheduler.run();
}

逻辑:

text 复制代码
scheduler starts Fiber A
  -> Fiber A calls fiber_sleep
  -> Fiber A suspended
scheduler starts Fiber B
  -> Fiber B suspended
scheduler starts Fiber C
  -> Fiber C suspended
timer ready
  -> resume corresponding fiber

12. awaityield 的逻辑差异

维度 await fiber yield
表达含义 等待某个 awaitable 完成 主动让出执行权
是否绑定结果 通常绑定结果 不一定
调度目标 event loop fiber scheduler
挂起范围 当前 coroutine 当前 fiber 的整个调用栈
调用限制 只能在 async def 中使用 通常可在 fiber 上下文中的普通函数中使用
编译/运行时模型 状态机式 coroutine 上下文切换式 coroutine

可以简单理解为:

text 复制代码
await = yield + 等待对象 + continuation

而 fiber 的 yield 更底层:

text 复制代码
yield = 保存当前 fiber 上下文,把控制权交给 scheduler

13. asyncio.gather 与 C++ when_all

13.1 asyncio.gather

python 复制代码
results = await asyncio.gather(a(), b(), c())

语义:

text 复制代码
等待 a、b、c 全部完成,返回结果列表。

13.2 C++ when_all

C++ 生态中很多异步库提供类似能力:

cpp 复制代码
auto result = co_await when_all(task_a(), task_b(), task_c());

语义:

text 复制代码
等待多个异步操作全部完成。

但 C++ 标准目前并没有一个 universally available 的 std::when_all 可直接覆盖所有 coroutine/fiber runtime。不同库的 taskfuturesender/receiver、scheduler 模型差异较大。


14. 工程选型建议

14.1 选择 Python asyncio 的场景

适合:

text 复制代码
1. Python 项目
2. 网络 I/O 高并发
3. HTTP API 服务
4. WebSocket 服务
5. 爬虫
6. 异步数据库访问
7. 消息队列消费者
8. 需要快速开发

不适合:

text 复制代码
1. CPU 密集型计算
2. 需要极致低延迟
3. 大量依赖同步阻塞库
4. 对运行时调度有强控制需求

14.2 选择 C++ Fiber 的场景

适合:

text 复制代码
1. 高性能服务器
2. 游戏服务器
3. RPC 框架
4. 低延迟网络系统
5. 希望用同步风格写异步逻辑
6. 需要 M:N 调度
7. 需要深层调用栈中任意挂起

不适合:

text 复制代码
1. 不想维护复杂 runtime
2. 项目团队不熟悉用户态调度
3. 调试工具链不完善
4. 对跨平台稳定性要求很高但库支持不足

15. 最重要的区别总结

15.1 抽象层级不同

text 复制代码
asyncio 是一个完整的异步 I/O 框架。
fiber 是一种执行上下文/调度单位。

15.2 栈模型不同

text 复制代码
asyncio coroutine 通常是 stackless。
fiber 通常是 stackful。

15.3 代码侵入性不同

asyncio 需要 async/await 贯穿调用链:

python 复制代码
async def a():
    await b()

fiber 可以让普通同步风格代码运行在可挂起上下文中:

cpp 复制代码
void a() {
    b();  // b 内部可以 yield
}

15.4 标准化程度不同

text 复制代码
Python asyncio 是标准库。
C++ fiber 没有统一标准运行时。

15.5 并发语义不同

text 复制代码
asyncio 更偏事件循环和 Future/Task。
fiber 更偏用户态线程和上下文切换。

16. 总结表

主题 Python asyncio C++ Fiber
本质 异步 I/O 框架 用户态轻量线程
调度者 event loop scheduler
调度方式 协作式 协作式
切换点 await yield / runtime wait
无栈 有栈
挂起范围 当前 coroutine 整个 fiber 调用栈
I/O 模型 非阻塞 I/O + event loop 非阻塞 I/O 或 hook 阻塞 I/O
并行能力 默认无,需要线程/进程 取决于 runtime,可做 M:N
标准支持 Python 标准库 C++ 无统一 fiber 标准
编程风格 显式 async/await 可接近同步风格
适用方向 Python 高并发 I/O C++ 高性能并发 runtime

17. 总结

text 复制代码
Python asyncio:用 event loop 调度无栈 coroutine,通过 await 显式挂起。
C++ fiber:用 scheduler 调度有栈用户态线程,通过 yield 或 runtime wait 挂起整个调用栈。

如果从概念映射看:

text 复制代码
asyncio Task      ≈ scheduled fiber
event loop        ≈ scheduler / reactor
await             ≈ suspend + yield to scheduler
gather            ≈ when_all
coroutine         ≈ coroutine / fiber task

但从实现机制看:

text 复制代码
asyncio 更像状态机;
fiber 更像用户态线程上下文切换。

经分析fiber在agent复杂任务并行请求调度更有优势.

相关推荐
qq_411262421 小时前
基于 ESP32-S3 的四博 AI 双目智能音箱工程设计:双目屏、触控、IMU、震动反馈、WebSocket 与 MCP 接入
人工智能·智能音箱
张二娃同学1 小时前
第03篇_CNN图像识别入门
人工智能·python·神经网络·cnn
电科一班林耿超1 小时前
机器学习大师课 第 8 课:端到端项目实战 —— 泰坦尼克号生存预测
人工智能·算法·机器学习
#卢松松#1 小时前
阿里云昨天上线团队版 Token Plan
人工智能
70asunflower1 小时前
7.2 回归 —— 预测一个数字
人工智能·数据挖掘·数据分析·回归
小张成长计划..1 小时前
【C++】30:C++11之lambda,新的类功能和包装器
c++
大龄程序员狗哥1 小时前
第51篇:AI伦理与偏见初探——你的模型“公平”吗?(概念入门)
人工智能
ComputerInBook1 小时前
数字图像处理(4版)——第 12 章——图像模式分类(上)(Rafael C.Gonzalez&Richard E. Woods)
图像处理·人工智能·算法·模式识别·图像模式分类
fengenrong1 小时前
APIO2026游记
c++