在 C++ 中实现协程的简易框架

协程(Coroutine)是一种可暂停、可恢复的函数,它允许你在执行过程中暂停并在后续恢复,从而实现更直观的异步编程。虽然 C++20 已经正式引入了协程语法(co_awaitco_yieldco_return),但在不依赖标准库的情况下,仍然可以通过手工状态机或状态机生成器实现简易协程。本文将演示如何在 C++17 环境下,利用 std::function、状态机和手动上下文切换,构建一个最小化的协程框架,并以生成斐波那契数列为例进行验证。


1. 设计思路

  1. 协程对象

    • 包含协程状态(执行中、完成、错误)
    • 存储当前状态机函数(lambda 或 functor)
    • 提供 resume() 方法以继续执行
  2. 上下文切换

    • 在每一次 resume() 调用时,根据内部状态决定执行哪一段代码
    • 状态机以整数标签表示,类似 switch 语句
  3. 协程返回值

    • 协程可以通过 yield 暂停并返回一个值,外部通过 next() 取得该值
    • 当协程结束时抛出 std::runtime_error 或使用特殊标记
  4. 错误处理

    • 采用异常机制捕获内部错误,并在外部通过 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. 代码说明

  1. Coroutine

    • resume() 负责执行状态机,并在 yield 处返回。
    • yield_value() 将值保存,并通过 label_ 暂停执行。
    • 状态机内部通过 CORO_BEGINCORO_YIELDCORO_END 宏实现类似 switch 的跳转。
  2. 状态机宏

    • CORO_BEGIN 初始化 switchcase 0 代表协程起点。
    • CORO_YIELD 将当前位置标记为 __LINE__,随后返回;下一次 resume 时会跳到对应 case
    • CORO_END 标记协程完成。
  3. 斐波那契实现

    • 使用 while 循环产生序列,每次 CORO_YIELD 暂停并返回当前值。
  4. 异常与错误

    • 若协程内部抛出异常,resume() 捕获并记录错误。

4. 进一步扩展

  1. 协程返回值

    • 通过 co_return 样式实现 return,在 CORO_END 前可加 return_value 存储。
  2. 协程池

    • 使用 std::vector<std::unique_ptr<Coroutine>> 管理多个协程,实现调度器。
  3. 异步 I/O

    • 在协程内将 I/O 操作包装为 awaitable,并在外部提供事件循环。
  4. 模板化

    • 通过模板参数实现不同返回类型、参数传递方式,提升通用性。

5. 小结

本文展示了在 C++17 环境下,利用手工状态机和宏定义,构建一个简易但功能完整的协程框架。虽然功能不如 C++20 标准协程丰富,但足以支持基本的异步流式数据处理。通过斐波那契示例,证明了该框架能够以直观的方式编写可暂停的逻辑,为进一步探索协程提供了良好的起点。

发表评论