C++20 协程原理解析与最小协程调度器实战实现

12 阅读1分钟

C++20 标准正式引入了 协程(coroutine)机制,这是一种新型的控制流工具,使得函数可以挂起、恢复、返回值并保持状态,极大提升了异步编程与任务调度的表达力。

本篇将全面讲解:

  • 协程基本概念与用法

  • C++20 协程语法结构

  • 协程关键类型详解(promise_type、handle)

  • 实现最小调度器与协程封装

  • 实战并发任务模型构建

一、什么是协程(Coroutine)

协程是一种可以中断(挂起)和恢复执行的函数,在保留执行上下文的同时可以与调度器协作完成:

  • 协程可 yield 多次值或挂起等待异步结果

  • 本质上是 状态机 + 栈帧保存

  • 对异步 I/O、任务调度、游戏逻辑非常有用

相比线程,协程是用户态级别的切换,开销远低于线程。

二、C++20 协程语法简介

cpp

复制编辑

#include <coroutine> std::generator<int> count_to_3() { co_yield 1; co_yield 2; co_yield 3; }

协程函数特征:

  • 使用 co_returnco_yieldco_await 关键字

  • 返回类型需满足协程语义(具有 promise_type)

三、协程机制核心组件解析

组件

作用

co_await

挂起协程等待,控制权交给调度器

co_yield

产出一个值,挂起等待下次 resume

co_return

协程返回

promise_type

编译器用于管理协程生命周期与状态

coroutine_handle

可控制协程 resume/destroy 的句柄对象

四、最小协程框架实现

我们从一个最小的返回 future 的协程函数开始实现。

4.1 定义 Awaitable 类型

cpp

复制编辑

struct SimpleAwaitable { bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle<>) const noexcept { std::cout << "suspend..." << std::endl; } void await_resume() const noexcept { std::cout << "resume..." << std::endl; } };

4.2 协程函数

cpp

复制编辑

std::future<void> my_coro() { co_await SimpleAwaitable{}; std::cout << "after await" << std::endl; }

五、构建一个协程返回类型(Task)

我们定义一个支持挂起、恢复和 co_awaitTask<T> 类型。

5.1 定义 Task

cpp

复制编辑

template<typename T = void> class Task { public: struct promise_type; using handle_type = std::coroutine_handle<promise_type>; Task(handle_type h) : coro(h) {} Task(const Task&) = delete; Task(Task&& t) noexcept : coro(t.coro) { t.coro = nullptr; } ~Task() { if (coro) coro.destroy(); } T get() { coro.resume(); return coro.promise().value; } struct promise_type { T value; Task get_return_object() { return Task{handle_type::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { std::exit(1); } void return_value(T v) { value = v; } }; private: handle_type coro; };

六、使用协程函数返回 Task

cpp

复制编辑

Task<int> compute() { std::cout << "Before await" << std::endl; co_return 42; }

调用方式:

cpp

复制编辑

int main() { auto t = compute(); int val = t.get(); std::cout << "Returned: " << val << std::endl; }

七、构建任务调度器(Scheduler)

为了支持多个任务并发运行,我们设计调度器结构如下:

7.1 调度器类定义

cpp

复制编辑

#include <queue> #include <coroutine> class Scheduler { public: void schedule(std::coroutine_handle<> handle) { tasks.push(handle); } void run() { while (!tasks.empty()) { auto h = tasks.front(); tasks.pop(); h.resume(); } } private: std::queue<std::coroutine_handle<>> tasks; };

7.2 定义一个 Awaitable 向调度器挂起协程

cpp

复制编辑

struct YieldAwaitable { Scheduler& scheduler; YieldAwaitable(Scheduler& sch) : scheduler(sch) {} bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle<> h) const { scheduler.schedule(h); } void await_resume() const noexcept {} };

八、并发任务协程示例

cpp

复制编辑

Task<void> async_work(Scheduler& sch, int id) { std::cout << "Task " << id << " step 1" << std::endl; co_await YieldAwaitable{sch}; std::cout << "Task " << id << " step 2" << std::endl; }

启动任务:

cpp

复制编辑

int main() { Scheduler scheduler; async_work(scheduler, 1); async_work(scheduler, 2); async_work(scheduler, 3); scheduler.run(); // round 1 scheduler.run(); // round 2 }

输出:

arduino

复制编辑

Task 1 step 1 Task 2 step 1 Task 3 step 1 Task 1 step 2 Task 2 step 2 Task 3 step 2

九、扩展设计:协程 + 定时器 + IO 等待

你可以将:

  • YieldAwaitable 变为 IO Awaitable(在 socket 可读时恢复)

  • Scheduler 与 event loop 配合

  • 添加超时 await(future + timer)

  • 将 Task 封装为 co_await + get_result

也可以加入线程池,让协程任务分发到不同线程:

cpp

复制编辑

co_await ThreadAwaitable{pool}; // 在线程池中 resume

十、C++ 协程适用场景

场景

协程优势

网络服务器

非阻塞 IO、按连接协程处理

游戏逻辑

每个 NPC 一个协程,逻辑清晰易管理

GUI 异步任务

界面不卡顿,用户体验流畅

并发任务链

将异步代码写成同步风格,逻辑清晰