实现一个简易的C++协程库:从概念到实践

C++20 引入了协程(coroutine)这一强大的语言特性,极大地简化了异步编程和生成器的实现。虽然标准库已经提供了 std::generatorstd::task 等原型,但对于想深入理解协程内部机制的程序员,自己实现一个最小可用的协程框架可以帮助掌握关键细节。本文将从协程的基本概念开始,逐步构建一个基于 coroutine_handlepromise_type 的简易协程库,并展示如何在实践中使用它。


1. 协程的基本构成

协程在 C++20 中以三大核心概念展开:

  1. 协程句柄(coroutine_handle:指向协程状态的指针,用来控制协程的挂起、恢复和销毁。
  2. 协程承诺(promise_type:负责协程的生命周期管理,定义返回值、异常处理等。
  3. 协程帧(coroutine_frame:存储局部变量和协程状态的堆栈帧。

协程的入口是一个返回 co_return 的函数,编译器会在内部生成一个 promise_type 并将协程句柄返回给调用方。协程在 co_yieldco_return 处暂停,调用方可以通过句柄恢复执行。


2. 设计简易协程框架

我们以实现一个通用 `generator

` 为例,该生成器在每次 `co_yield` 时产生一个值,直到协程结束。 ### 2.1 基本框架 “`cpp #include #include #include #include #include template class generator { public: struct promise_type { std::optional current_value; generator get_return_object() { return generator{std::coroutine_handle ::from_promise(*this)}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } std::suspend_always yield_value(T value) { current_value = std::move(value); return {}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; using handle_type = std::coroutine_handle ; explicit generator(handle_type h) : coro(h) {} generator(const generator&) = delete; generator& operator=(const generator&) = delete; generator(generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; } ~generator() { if (coro) coro.destroy(); } bool move_next() { if (!coro.done()) { coro.resume(); return !coro.done(); } return false; } T current_value() const { return *coro.promise().current_value; } private: handle_type coro; }; “` **关键点说明** – `promise_type::yield_value` 在协程挂起时把值放入 `current_value`。 – `initial_suspend` 与 `final_suspend` 都返回 `suspend_always`,保证协程在创建后立即挂起,结束时也会挂起,便于资源回收。 – `generator::move_next` 负责恢复协程并检查是否已结束。 ### 2.2 使用示例 “`cpp generator countdown(int start) { for (int i = start; i >= 0; –i) co_yield i; // 每次循环产生一个值 } int main() { for (auto gen = countdown(5); gen.move_next(); ) { std::cout ` 的 `promise_type::return_value` 改为 `T` 或 `std::optional`。在 `final_suspend` 里读取并返回。 ### 3.3 并发协程 将协程与线程或任务调度器结合,可实现高效的异步 IO。典型做法是将 `generator` 与 `std::future` 或 `asio` 事件循环配合,使用 `co_await` 代替 `co_yield` 进行异步等待。 — ## 4. 性能与优化 – **避免堆分配**:在 `generator` 的实现中,协程帧是由编译器在栈上分配的(若协程不跨越大内存),因此大多数情况下不需要显式堆分配。 – **减少拷贝**:使用 `std::optional ` 或 `std::unique_ptr` 来缓存值,避免不必要的拷贝。 – **内联**:编译器会把 `suspend_always` 生成的状态机内联,降低协程开销。 — ## 5. 小结 本文通过实现一个最小化的 `generator`,深入剖析了 C++20 协程的核心概念:句柄、承诺和帧。你可以在此基础上继续扩展更复杂的协程模式,例如 `async_task`、`select`、并发流水线等。掌握协程的内部机制,能让你在编写高性能异步代码时更加得心应手。祝你编码愉快!

发表评论