### 如何在C++中实现协程:从C++20到自定义协程库

协程(Coroutines)是一种轻量级的协作式多任务实现方式,能够让函数在执行过程中暂停并恢复,极大地简化异步编程和生成器的实现。自 C++20 起,标准库已经提供了协程支持,但在实际项目中,很多人仍然倾向于使用第三方协程库或自己实现微协程。本文将从语言特性、标准库实现、常见错误以及如何自行实现一个简单协程框架等方面进行详细解析。


1. C++20 协程的基本语法

C++20 引入了 co_awaitco_yieldco_return 三个关键字,配合 std::futurestd::generator 等类型实现协程。一个最小的协程示例:

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>

struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
    };
};

Task simpleCoroutine() {
    std::cout << "Start\n";
    co_return;
}

int main() {
    simpleCoroutine();
    return 0;
}

此协程立即执行到结束,因为 initial_suspendfinal_suspend 都返回 suspend_never


2. 协程与传统异步模型的区别

传统异步 协程
需要回调函数 代码可读性高,像同步代码
线程/事件循环 轻量级协作
错误处理复杂 通过异常传播

协程的核心优势在于把“暂停点”显式化,消除了回调金字塔。


3. 常见错误与调试技巧

  1. 忘记 co_return
    协程没有 co_return 会导致 main 返回未定义状态。
    调试技巧:使用编译器警告 -Wreturn-type

  2. 错误的 Promise 实现
    promise_type 的生命周期与协程的生命周期紧密相关。
    调试技巧:在 promise_type 构造和析构中打印日志。

  3. 忘记 std::suspend_always/suspend_never
    让协程一直挂起或一直执行,导致无限循环。
    调试技巧:使用 std::suspend_always 作为默认暂停策略。

  4. 栈溢出
    过深的递归协程会导致栈溢出。
    调试技巧:限制递归深度,或改用迭代。


4. 自己实现一个简易协程框架

虽然标准库提供了完整实现,但有时我们想要更细粒度的控制,例如自定义协程调度器。下面给出一个最小化协程框架的实现,支持 yieldawait

4.1 基础类型

#include <functional>
#include <vector>
#include <memory>

class Coroutine;

using Task = std::function<void(Coroutine&)>;

class Coroutine {
public:
    Coroutine(Task func) : func_(std::move(func)), finished_(false) {}

    void resume() {
        if (!finished_) func_(*this);
    }

    void yield() {
        // 暂停当前协程,控制权交给调度器
        // 这里简单实现直接返回
    }

    void finish() { finished_ = true; }

    bool finished() const { return finished_; }

private:
    Task func_;
    bool finished_;
};

4.2 调度器

class Scheduler {
public:
    void add(Coroutine::Task t) {
        coros_.emplace_back(std::make_shared <Coroutine>(std::move(t)));
    }

    void run() {
        while (!coros_.empty()) {
            auto c = coros_.front();
            c->resume();
            if (c->finished()) {
                coros_.erase(coros_.begin());
            }
        }
    }

private:
    std::vector<std::shared_ptr<Coroutine>> coros_;
};

4.3 示例:生成斐波那契数列

int main() {
    Scheduler sched;

    sched.add([](Coroutine& self) {
        int a = 0, b = 1;
        for (int i = 0; i < 10; ++i) {
            std::cout << a << " ";
            int next = a + b;
            a = b;
            b = next;
            self.yield();  // 暂停
        }
        self.finish();
    });

    sched.run();
}

该框架非常基础,缺少异常传播、状态保存、协程间通信等高级特性。但它展示了协程的核心概念:一个协程是一个可暂停、可恢复的执行单元,调度器负责管理其生命周期


5. 进一步学习与实践

  • Boost.Coroutine:成熟的协程库,支持多种调度策略。
  • Libco:轻量级协程库,适合嵌入式或高并发场景。
  • C++20 协程实验室:在现代 IDE 中尝试 co_awaitco_yield

实践建议:先在一个小项目中使用标准协程实现异步 IO,然后逐步尝试自己实现调度器,了解协程的底层机制。


6. 小结

C++20 的协程为异步编程提供了强大的工具。掌握 co_awaitco_yield 的使用后,编写异步代码会像写同步代码一样自然。标准库提供的 std::generatorstd::future 等类型已经足够满足大多数需求,但对细粒度控制有需求的项目,可以尝试自己实现轻量级协程框架。通过本文的代码示例与调试技巧,读者可以快速上手并深入理解协程的本质。

祝编码愉快,协程无极限!

发表评论