C++ 中有栈协程(Stackful Coroutines)和无栈协程(Stackless Coroutines)的核心区别在于执行上下文的存储方式和调度灵活性,以下是两者的对比:
1. 核心差异
特性 | 有栈协程 | 无栈协程 |
---|---|---|
栈空间 | 每个协程拥有独立栈内存 | 无独立栈,依赖编译器生成的状态机 |
挂起点 | 可在任意函数调用层级挂起 | 只能在协程函数内显式标记的位置(如 co_await )挂起 |
上下文切换开销 | 较高(需保存/恢复完整栈) | 较低(仅保存局部变量和状态) |
内存占用 | 较大(每协程需预分配栈空间) | 较小(内存占用与状态复杂度相关) |
典型实现 | Boost.Coroutine2、libco 等 | C++20 标准协程(std::coroutine ) |
2. 实现原理
-
有栈协程
通过手动或自动分配独立栈空间,利用上下文切换(如swapcontext
或汇编指令)保存/恢复寄存器及栈指针。协程可在任意嵌套调用中挂起,但栈空间固定(可能溢出)。 -
无栈协程
由编译器将协程函数转换为状态机,通过promise_type
和coroutine_handle
管理状态。挂起时仅保留必要的局部变量(存储在堆或优化后的栈帧中),无法在非协程函数内挂起。
3. 代码示例对比
有栈协程(伪代码,基于 Boost.Coroutine2)
#include <boost/coroutine2/all.hpp>
using namespace boost::coroutines2;
void coro_func(coroutine<void>::push_type& yield) {
yield(); // 可在任意位置挂起
}
int main() {
coroutine<void>::pull_type coro(coro_func);
coro(); // 启动协程并挂起
}
无栈协程(C++20 标准)
#include <coroutine>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
Task my_coroutine() {
co_await std::suspend_always{}; // 只能在显式标记处挂起
}
int main() {
auto coro = my_coroutine();
}
4. 适用场景
-
有栈协程
- 需要深层嵌套调用或复杂控制流(如递归算法)。
- 依赖第三方库且无法修改代码为协程风格。
- 对性能要求不苛刻,但需要高度灵活性。
-
无栈协程
- 高并发、轻量级任务(如异步 I/O、生成器)。
- 需要与 C++ 标准库无缝集成(如
co_await
异步操作)。 - 注重内存效率和低延迟切换。
5. 关键限制
-
有栈协程
- 栈空间预分配可能导致内存浪费或溢出。
- 上下文切换开销较高,不适合超大规模并发。
-
无栈协程
- 无法在非协程函数内挂起(如第三方库的回调)。
- 调试复杂(状态机转换导致代码可读性下降)。
总结
有栈协程 | 无栈协程 | |
---|---|---|
优势 | 灵活性高、兼容旧代码 | 内存高效、性能优 |
劣势 | 资源消耗大、切换慢 | 语法约束多、调试难 |
根据项目需求选择:若需要极致性能且能适配协程范式,优先无栈协程;若需深度控制或兼容传统代码,则有栈协程更合适。