
你有没有发现,在传统的电信行业,分布式数据库很少使用协程(Coroutine),而到了文件系统领域,3FS (Fire-Flyer File System) 直接采用 Facebook 开源的 Folly 提供协程。
画外音:
此刻这个不是主要解决的,我是使用者,我最关系的协程帮助我解决什么问题
Folly 是面向多核架构和硬件特性进行设计的。Folly 的强大之处在于它将协程的轻量级并发与线程池的并行性结合了起来。
单线程内的并发
•
一个线程通过协程可以高效处理数万甚至数十万个并发 I/O 操作
•
通过 folly::coro::Task 配合异步 I/O 原语(如 io_uring)实现
跨线程的并行
•
通过 folly::IOThreadPoolExecutor 等线程池分发任务
•
将大量协程任务分发到多个线程上并行执行
•
直接利用多核 CPU 的计算能力,提升系统整体吞吐量
新一代 Ceph 直接利用异步框架 Seastar 重构 OSD。
Seastar 核心特性:
•
高性能:借助于无锁编程和精心设计的内存管理,能在多核 CPU 上实现接近硬件极限的性能
•
异步非阻塞:使用未来(futures)简化复杂异步逻辑的编码过程
•
自含 TCP/IP 栈:内置的高性能网络栈绕过操作系统瓶颈
多线程场景分析: 在传统电信行业,一台 100G/64 核服务器上,任务有积压,通过多线程方式提高并发,这个并发本质上是依赖多核并行计算。
核心定义对比:
概念 | 定义 | 关键特征 |
|---|---|---|
并发 | 逻辑上的同时发生 | 系统具有处理多个任务的能力,但不一定在同一时刻执行 |
并行 | 物理上的同时执行 | 必须在同一时刻有多个任务正在被执行 |
技术洞察:
•
计算是并行执行,但访问共享资源需要串行执行
•
从互斥锁演进到无锁编程
•
epoll_wait 使单个线程能高效管理数万并发连接
•
事件驱动方式让 CPU 仅在任务就绪时处理,避免资源浪费
重要结论:
•
协程本身不提供并行能力
•
一个线程内的所有协程,在任何时刻只有一个在运行
•
协程是并发而非并行的
•
核心价值:解决高并发问题,特别在 I/O 密集型场景
核心问题:协程库如何提高并发能力?
传统答案的问题:
•
协程库专注解决单个线程内用户态调用问题
•
并发问题通过开启多个进程解决
•
但进程消耗内存过大(如 10GB/进程)
•
这种方式不通用,未大规模推广
1
多进程(MP)时代
•
代表:Apache web server
•
问题:创建进程开销过高
•
调度单位:进程
2
多线程(MT)时代
•
改进:创建线程服务新用户
•
问题:线程上下文切换、锁竞争
•
调度单位:线程
3
事件驱动状态机(EDSM)
•
基于 I/O 复用机制处理大量并发
•
问题:程序一体化,开发复杂

核心优势:
•
调度单位减小到函数级别
•
上下文切换不需要内核参与
•
无系统调用、无锁竞争、无信号处理
•
保持线性处理逻辑,提高开发效率
// 伪代码示意
coroutine A() {
do_something();
yield; // 暂停点
continue_work(); // 恢复后从这里开始
}
执行机制:
•
暂停时保存执行上下文(寄存器、局部变量等)
•
恢复时恢复之前保存的上下文
•
实现"从上次离开的地方继续"
类型 | 特点 | 代表 | 适用场景 |
|---|---|---|---|
有栈协程 | 独立调用栈,任意深度 yield | Go goroutine | 通用性强 |
无栈协程 | 无独立调用栈,函数内 yield | C++20、Python generator | 性能极致 |
Async/Await | Promise/Future 语法糖 | 现代语言主流 | 开发友好 |
技术类比:
•
Stackful ≈ 完整的虚拟机
•
Stackless ≈ 优化的状态机
•
Async/Await ≈ 统一的编排层
白银级问题:C++20 原生协程如何提高多核并行能力?
架构设计:
•
使用 Seastar 框架重写 OSD
•
I/O 操作建模为协程任务
•
客户端请求包装成协程任务
并行策略:
// 执行示例输出
主线程: 140735273355072
开始分配10个协程任务到线程池...
协程 0 开始处理, 数据: 0, 线程: 123145447251968
协程 1 开始处理, 数据: 10, 线程: 123145452507136
协程 2 开始处理, 数据: 20, 线程: 123145457762304
技术优势:
•
Seastar 采用 share-nothing 架构
•
避免线程间锁竞争
•
协程任务分散到不同 CPU 核心
•
实现高度的并行处理能力
📂 libfork - 基于无栈协程的工作窃取
•
架构:无锁细粒度并行库 + 用户空间几何分段栈
•
调度:Continuation stealing(工作窃取)策略
•
性能:相比 OpenMP 快 7.2 倍,内存减少 10 倍
📂 concurrencpp - C++ 并发库
•
特性:通过任务、执行器和协程编写并发应用
•
执行器:thread_pool_executor、worker_thread_executor
关键要点:
•
执行器选择:决定协程任务在哪个线程执行
•
线程亲和性:注意协程可能在不同线程恢复执行
•
工作窃取:计算密集型任务的有效负载均衡策略
void traditional_thread_function(int id) {
int local_var = id; // 在线程栈上
std::string data = "data"; // 在线程栈上
// 变量绑定到特定线程栈,线程结束数据丢失
}
问题根源:执行上下文与执行线程强绑定
CoroutineTask async_operation(int id) {
int local_var = id; // 在协程帧中
std::string data = "data"; // 在协程帧中
co_await std::suspend_always{}; // 挂起点
// 即使线程切换,变量状态保持不变
std::cout << local_var << ", " << data << std::endl;
co_return;
}
核心机制:
1
编译时转换:编译器将协程函数转换为状态机
2
协程帧结构:生成包含所有局部变量的结构体
3
堆分配:默认通过 operator new 分配协程帧
4
状态管理:使用 resume_point 跟踪执行位置
内存模型对比:
类型 | 变量存储 | 生命周期 | 线程安全 |
|---|---|---|---|
传统函数 | 线程栈 | 函数调用期间 | 线程绑定 |
协程 | 堆上协程帧 | 协程句柄生命周期 | 线程间安全迁移 |
关键洞察:C++20 协程本质是编译器支持的有限状态机,局部变量被"提升"到堆上的状态结构中。

Folly(Facebook Open Source Library)是专为 C++17 设计的组件库,核心宗旨是实用与高效。

核心方法 - tryStartAllocateTask:
void tryStartAllocateTask(folly::Executor *exec) {
// 确保同时只有一个分配任务在运行
if(!allocating_.exchange(true)) {
startAllocateTask(exec);
}
}
设计优势:
•
非阻塞预分配机制
•
避免每次分配 ID 都访问 FoundationDB
•
批量分配 4096 个 ID 提高性能
•
协程实现异步,不阻塞主线程
folly::Executor 是 Folly 核心组件,为协程提供统一执行抽象。
核心功能:
•
任务调度:提交协程任务到合适线程池
•
执行上下文:提供运行环境和资源管理
•
异步控制:控制协程启动、暂停和恢复
startAllocateTask 实现:
void startAllocateTask(folly::Executor *exec) {
folly::RequestContextScopeGuard guard;
allocateTask(weak_from_this()).scheduleOn(exec).start();
}
技术要点:
•
请求上下文管理确保正确性
•
使用 weak_from_this() 避免循环引用
•
在指定执行器上调度协程任务
co_await 核心作用详解三阶段执行模型:
1
挂起(Suspend):遇到未完成的 awaitable 时挂起协程
2
等待(Wait):等待 awaitable 对象结果可用
3
恢复(Resume):awaitable 完成时恢复执行并获取结果
性能优化指南:
•
CPU 密集型任务:使用线程池执行器
•
IO 密集型任务:使用 IO 执行器
•
轻量级任务:使用串行执行器
从传统电信行业到现代文件系统,协程技术经历了显著的演进。
3FS 通过 Folly 库成功将协程的轻量级并发与线程池的并行性相结合,
实现了在多核架构下的高性能 I/O 处理。
核心认知:
•
协程本身解决并发问题
•
通过与合适的执行器和线程池配合,才能充分发挥多核并行优势
•
C++20 协程的执行上下文与执行线程解耦是技术突破的关键
•
现代协程框架通过工作窃取、负载均衡等策略实现多核高效利用