协程(Coroutine)是一种可暂停、可恢复的函数,它允许你在执行过程中暂停并在后续恢复,从而实现更直观的异步编程。虽然 C++20 已经正式引入了协程语法(co_await、co_yield、co_return),但在不依赖标准库的情况下,仍然可以通过手工状态机或状态机生成器实现简易协程。本文将演示如何在 C++17 环境下,利用 std::function、状态机和手动上下文切换,构建一个最小化的协程框架,并以生成斐波那契数列为例进行验证。
1. 设计思路
-
协程对象
- 包含协程状态(执行中、完成、错误)
- 存储当前状态机函数(lambda 或 functor)
- 提供
resume()方法以继续执行
-
上下文切换
- 在每一次
resume()调用时,根据内部状态决定执行哪一段代码 - 状态机以整数标签表示,类似
switch语句
- 在每一次
-
协程返回值
- 协程可以通过
yield暂停并返回一个值,外部通过next()取得该值 - 当协程结束时抛出
std::runtime_error或使用特殊标记
- 协程可以通过
-
错误处理
- 采用异常机制捕获内部错误,并在外部通过
status()检查
- 采用异常机制捕获内部错误,并在外部通过
2. 代码实现
#include <iostream>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
// --------------------- 协程状态枚举 ---------------------
enum class CoroutineStatus {
Ready,
Running,
Done,
Error
};
// --------------------- 简易协程类 ---------------------
class Coroutine {
public:
// 构造函数,传入状态机函数
explicit Coroutine(std::function<void(Coroutine&)> func)
: state_(CoroutineStatus::Ready), func_(std::move(func)), label_(0) {}
// 恢复协程
void resume() {
if (state_ == CoroutineStatus::Done || state_ == CoroutineStatus::Error)
return;
state_ = CoroutineStatus::Running;
try {
func_(*this); // 运行状态机
if (state_ != CoroutineStatus::Done)
state_ = CoroutineStatus::Ready; // 若未结束,保持可再次 resume
} catch (const std::exception& e) {
state_ = CoroutineStatus::Error;
error_msg_ = e.what();
}
}
// 产出值
int next() {
if (!has_value_) throw std::runtime_error("No value yielded");
has_value_ = false;
return yielded_;
}
// 检查协程是否完成
bool done() const { return state_ == CoroutineStatus::Done; }
// 访问错误信息
std::string error() const { return error_msg_; }
// 协程内部调用:yield 一个值并返回
void yield_value(int val) {
yielded_ = val;
has_value_ = true;
label_ = current_label_;
state_ = CoroutineStatus::Ready;
}
// 内部用于状态机标记
int current_label_;
int label_;
private:
CoroutineStatus state_;
std::function<void(Coroutine&)> func_;
int yielded_;
bool has_value_{false};
std::string error_msg_;
};
// --------------------- 状态机生成器 ---------------------
// 通过宏简化状态机代码
#define CORO_BEGIN(cor) \
switch ((cor).label_) { case 0:
#define CORO_YIELD(cor, val) \
do { \
(cor).current_label_ = __LINE__; \
(cor).yield_value(val); \
return; case __LINE__: ; \
} while (0)
#define CORO_END(cor) \
} (cor).state_ = CoroutineStatus::Done;
// --------------------- 示例:斐波那契数列 ---------------------
Coroutine fibonacci(int n) {
return Coroutine([n](Coroutine& c) {
int a = 0, b = 1;
int i = 0;
CORO_BEGIN(c);
while (i < n) {
CORO_YIELD(c, a);
int tmp = a + b;
a = b;
b = tmp;
++i;
}
CORO_END(c);
});
}
// --------------------- 主函数 ---------------------
int main() {
auto fib = fibonacci(10);
std::cout << "斐波那契数列前 10 项:" << std::endl;
while (!fib.done()) {
fib.resume();
if (fib.done()) break;
std::cout << fib.next() << " ";
}
std::cout << std::endl;
return 0;
}
3. 代码说明
-
Coroutine类resume()负责执行状态机,并在yield处返回。yield_value()将值保存,并通过label_暂停执行。- 状态机内部通过
CORO_BEGIN、CORO_YIELD、CORO_END宏实现类似switch的跳转。
-
状态机宏
CORO_BEGIN初始化switch,case 0代表协程起点。CORO_YIELD将当前位置标记为__LINE__,随后返回;下一次resume时会跳到对应case。CORO_END标记协程完成。
-
斐波那契实现
- 使用
while循环产生序列,每次CORO_YIELD暂停并返回当前值。
- 使用
-
异常与错误
- 若协程内部抛出异常,
resume()捕获并记录错误。
- 若协程内部抛出异常,
4. 进一步扩展
-
协程返回值
- 通过
co_return样式实现return,在CORO_END前可加return_value存储。
- 通过
-
协程池
- 使用
std::vector<std::unique_ptr<Coroutine>>管理多个协程,实现调度器。
- 使用
-
异步 I/O
- 在协程内将 I/O 操作包装为
awaitable,并在外部提供事件循环。
- 在协程内将 I/O 操作包装为
-
模板化
- 通过模板参数实现不同返回类型、参数传递方式,提升通用性。
5. 小结
本文展示了在 C++17 环境下,利用手工状态机和宏定义,构建一个简易但功能完整的协程框架。虽然功能不如 C++20 标准协程丰富,但足以支持基本的异步流式数据处理。通过斐波那契示例,证明了该框架能够以直观的方式编写可暂停的逻辑,为进一步探索协程提供了良好的起点。