协程是 C++20 引入的一种能暂停执行、保留状态并在后续恢复执行的函数,突破了传统函数 “一次性执行完毕” 的线性执行模式。
先看代码,后文有说明。
使用示例
```c++
/**
- C++20 协程示例:co_await 与自定义 Awaitable
- 编译:需 -std=c++20 -fcoroutines
- 运行:./coroutine_await
-
- 演示如何实现一个最小 Task 类型,以及自定义 Awaitable(SleepAwaitable)的用法。
*/
include <chrono>
include <coroutine>
include <iostream>
include <thread>
// ========== 关键点1:自定义 Awaitable 需实现三个接口 ==========
// co_await expr 时,expr 必须是 Awaitable(或通过 operator co_await 转换)
struct SleepAwaitable
{
std::chrono::milliseconds duration;
// 若为 true,不挂起,直接继续执行;这里 0 及以下不挂起
bool await_ready() const { return duration.count() <= 0; }
// ========== 关键点2:挂起时调用,h 就是当前协程的句柄 ==========
// 在此把 h 交给“谁”来在将来 resume:这里用线程 sleep 后 resume
// 实际项目中可改为:注册到 I/O 完成回调、定时器、线程池等
void await_suspend(std::coroutine_handle<> h) const
{
std::thread([h, d = duration]() {
std::this_thread::sleep_for(d);
h.resume(); // 在“就绪”后恢复协程
}).detach();
}
// 恢复时调用;返回值即为 co_await 表达式的值,这里为 void
void await_resume() {}
};
// ========== 关键点3:Task 通过 promise_type 与协程绑定 ==========
// 支持在协程内使用 co_await,并让调用方能启动/等待协程
struct Task
{
struct promise_type
{
Task get_return_object()
{
return Task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
// 初始挂起:协程创建后先不执行,等有人 resume 再跑
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> handle;
void start() { handle.resume(); }
bool done() const { return handle.done(); }
~Task()
{
if (handle)
handle.destroy();
}
};
// ========== 关键点4:协程内 co_await Awaitable,会挂起并在 Awaitable 恢复时继续 ==========
Task run_delayed_echo()
{
std::cout << " [协程] 开始,即将挂起 500ms..." << std::endl;
co_await SleepAwaitable{std::chrono::milliseconds(500)}; // 挂起约 500ms 后从此行之后恢复
std::cout << " [协程] 恢复执行,再挂起 300ms..." << std::endl;
co_await SleepAwaitable{std::chrono::milliseconds(300)};
std::cout << " [协程] 再次恢复,结束。" << std::endl;
co_return; // 协程正常结束,由 promise_type::return_void() 处理
}
int main()
{
std::cout << "=== co_await 自定义 Awaitable(Sleep)===" << std::endl;
std::cout << "[main] 创建协程并启动" << std::endl;
Task t = run_delayed_echo();
t.start();
// 第一次 resume:协程跑到第一个 co_await 后挂起
// 协程在另一线程里被 SleepAwaitable 恢复,主线程简单等待其结束
while (!t.done())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "[main] 协程已结束" << std::endl;
return 0;
}
输出如下
=== co_await 自定义 Awaitable(Sleep)===
[main] 创建协程并启动
[协程] 开始,即将挂起 500ms...
[协程] 恢复执行,再挂起 300ms...
[协程] 再次恢复,结束。
[main] 协程已结束
协程的核心定义
协程本质是可中断的函数,通过 co_await、co_yield、co_return 等关键字实现执行流的灵活控制。
co_await 是协程暂停的核心入口,其操作对象需满足 “Awaitable” 接口(示例中 SleepAwaitable),协程执行到 co_await 时会暂停,直到 Awaitable 对象触发恢复;
- 协程的行为由
promise_type(示例中 Task::promise_type)管控,它定义了协程的创建(get_return_object)、初始 / 最终挂起(initial_suspend/final_suspend)、返回值处理(return_void)等核心规则,是协程与外部交互的桥梁;
- 协程句柄(
std::coroutine_handle)是操控协程的 “手柄”,可通过 resume() 恢复执行、done() 判断是否结束、destroy() 释放资源,示例中 Task 类封装了句柄,提供 start() 启动协程的便捷接口。
协程的关键特性
- 可控的挂起与恢复:协程可在任意
co_await 处主动挂起,且挂起时会保留当前栈帧和变量状态(示例中 run_delayed_echo 协程两次挂起后,恢复时能从挂起行继续执行),无需像线程那样重新初始化上下文;
- 非阻塞等待:通过自定义 Awaitable(如
SleepAwaitable),协程挂起时不会阻塞当前线程,示例中挂起逻辑被移交到新线程执行 sleep_for,主线程可继续做其他操作,仅通过轮询 done() 等待协程结束,相比传统阻塞等待更高效,这对于设计高并发的后端系统架构很有启发;
- 轻量级:协程的挂起 / 恢复开销远低于线程切换,示例中通过协程实现的延迟执行,无需创建多个重量级线程,仅通过协程句柄的
resume() 即可恢复执行;
- 自定义等待逻辑:Awaitable 对象可灵活定制挂起后的行为(示例中是延迟恢复,实际可扩展为 I/O 完成回调、定时器触发、线程池调度等),只需实现
await_ready()(判断是否需要挂起)、await_suspend()(挂起时的逻辑,如移交协程句柄)、await_resume()(恢复时的返回值处理)三个接口。深入理解这种机制,是掌握现代 C/C++ 高级编程的关键一步。
通过上面的代码与解析,我们可以直观地感受到 C++20 协程带来的执行流程控制能力。从自定义 Awaitable 的实现,到 Task 类型对协程生命周期的封装,每一步都体现了标准库设计的灵活性。如果你对更多 C++ 高级特性和并发编程实践感兴趣,欢迎到云栈社区与其他开发者交流探讨。

|