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_return
、co_yield
、co_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_await
的 Task<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 异步任务
界面不卡顿,用户体验流畅
并发任务链
将异步代码写成同步风格,逻辑清晰