50.C++ Coroutine

2026-01-23
2562 字 · 13 分钟

🔬 C++ Coroutine

📋 总结

📖 内容概览 本文将详细介绍C++20中引入的协程(Coroutine)特性,包括协程的基本概念、核心原理、编译器转换机制、Promise和Awaitable对象的设计,以及协程在异步编程中的应用。通过深入理解协程的工作原理,帮助开发者掌握这一强大的异步编程工具。 📚 协程的基本概念

1.1 什么是协程

协程(Coroutine)是一种特殊的函数,可以暂停执行在稍后恢复执行,同时保留其执行状态。与传统函数不同,协程可以:

  • 在执行过程中暂停,将控制权返回给调用者
  • 从暂停的地方继续执行,保留之前的局部变量和执行状态
  • 与其他协程或线程协作执行

1.2 C++协程的关键字

C++20引入了三个新关键字来支持协程:

关键字功能描述
co_await暂停协程,等待某个操作完成后恢复
co_return从协程返回一个结果并结束协程
co_yield返回一个结果并暂停协程,允许后续恢复执行

1.3 协程的特点

  • 轻量级:协程的创建和切换开销远小于线程
  • 协作式:协程的暂停和恢复由程序员显式控制
  • 无栈:C++协程是无栈协程,不需要为每个协程分配独立的栈空间
  • 状态保存:自动保存和恢复局部变量和执行状态
  • 异步友好:非常适合异步编程模型 🔧 协程的编译转换 当编译器遇到包含协程关键字的函数时,会将其转换为一个复杂的状态机结构。这个转换过程是理解协程工作原理的关键。

2.1 协程函数的基本形式

一个简单的协程函数示例:

template <typename TRet, typename... TArgs>
TRet coro_func(TArgs... args) {
// 函数体包含至少一个协程关键字
co_return some_value;
}

2.2 编译器的转换过程

编译器会将上述协程函数转换为类似以下的形式:

// 1. 确定Promise类型
using promise_t = typename coroutine_traits<TRet, TArgs...>::promise_type;
// 2. 创建Promise对象
promise_t promise;
// 3. 获取返回对象
auto __return_obj__ = promise.get_return_object();
// 4. 初始挂起
co_await promise.initial_suspend();
try {
// 5. 协程主体
// 编译器会将原函数体转换为状态机
// co_return expr; => promise.return_value(expr); goto final_suspend;
// co_return; => promise.return_void(); goto final_suspend;
// co_yield expr; => co_await promise.yield_value(expr);
} catch (...) {
// 6. 异常处理
promise.set_exception(std::current_exception());
}
final_suspend:
// 7. 最终挂起
co_await promise.final_suspend();

🎯 协程的核心组件

3.1 Promise类型

Promise是协程的核心组件之一,负责:

  • 创建协程的返回对象
  • 处理协程的返回值(通过return_valuereturn_void
  • 处理协程产生的值(通过yield_value
  • 处理协程抛出的异常(通过set_exception
  • 控制协程的初始和最终挂起行为 Promise类型必须实现以下接口(部分是可选的): | 方法 | 必须实现? | 功能描述 | |------|-----------|----------| | get_return_object() | 是 | 返回协程的结果对象 | | initial_suspend() | 是 | 控制协程是否在开始时挂起 | | final_suspend() noexcept | 是 | 控制协程在结束时的挂起行为 | | return_value(T) | 是(有返回值时) | 处理协程的返回值 | | return_void() | 是(无返回值时) | 处理协程的无返回值情况 | | yield_value(T) | 否(生成值时) | 处理协程产生的值 | | set_exception(std::exception_ptr) | 否 | 处理协程抛出的异常 |

3.2 Awaitable对象

Awaitable对象是可以被co_await等待的对象,必须实现以下三个方法:

方法功能描述
await_ready()检查是否已准备好,返回true则跳过挂起
await_suspend(coroutine_handle<>)挂起协程,返回值控制后续行为
await_resume()恢复协程时返回结果

3.3 coroutine_handle

std::coroutine_handle<>是一个轻量级的句柄,用于控制协程的执行:

  • resume():恢复协程执行
  • destroy():销毁协程
  • done():检查协程是否已完成
  • promise():获取协程关联的Promise对象 💻 协程的实现示例

4.1 简单的生成器示例

下面实现一个简单的整数生成器,使用协程生成从1到n的整数:

#include <iostream>
#include <coroutine>
// 生成器的返回类型
template <typename T>
struct Generator {
// Promise类型
struct promise_type {
T value;
std::exception_ptr exception;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
void unhandled_exception() noexcept {
exception = std::current_exception();
}
template <std::convertible_to<T> From>
std::suspend_always yield_value(From&& from) noexcept {
value = std::forward<From>(from);
return {};
}
void return_void() noexcept {
}
};
std::coroutine_handle<promise_type> handle;
explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() {
if (handle) handle.destroy();
}
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
Generator(Generator&& other) noexcept : handle(other.handle) {
other.handle = nullptr;
}
Generator& operator=(Generator&& other) noexcept {
if (this != &other) {
if (handle) handle.destroy();
handle = other.handle;
other.handle = nullptr;
}
return *this;
}
// 迭代器支持
struct iterator {
std::coroutine_handle<promise_type> handle;
bool done;
iterator(std::coroutine_handle<promise_type> h, bool d) : handle(h), done(d) {}
iterator& operator++() {
handle.resume();
done = handle.done();
return *this;
}
T operator*() const {
return handle.promise().value;
}
bool operator!=(const iterator& other) const {
return done != other.done;
}
};
iterator begin() {
handle.resume();
return {handle, handle.done()};
}
iterator end() {
return {handle, true};
}
};
// 协程函数:生成从1到n的整数
Generator<int> generate_range(int n) {
for (int i = 1; i <= n; ++i) {
co_yield i;
}
}
int main() {
// 使用生成器
for (int i : generate_range(5)) {
std::cout << i << " ";
}
// 输出: 1 2 3 4 5
return 0;
}

4.2 异步任务示例

下面实现一个简单的异步任务,演示协程在异步编程中的应用:

#include <iostream>
#include <future>
#include <thread>
#include <coroutine>
#include <chrono>
// 异步任务的返回类型
struct AsyncTask {
struct promise_type {
std::promise<int> result_promise;
AsyncTask get_return_object() {
return AsyncTask{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() noexcept {
return {};
}
std::suspend_never final_suspend() noexcept {
return {};
}
void unhandled_exception() noexcept {
result_promise.set_exception(std::current_exception());
}
void return_value(int value) noexcept {
result_promise.set_value(value);
}
};
std::coroutine_handle<promise_type> handle;
explicit AsyncTask(std::coroutine_handle<promise_type> h) : handle(h) {}
~AsyncTask() {
if (handle) handle.destroy();
}
// 获取异步结果
std::future<int> get_future() {
return handle.promise().result_promise.get_future();
}
};
// 模拟耗时操作
void sleep_ms(int ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
// 异步任务协程
AsyncTask async_operation(int value, int delay_ms) {
// 模拟耗时操作
sleep_ms(delay_ms);
co_return value * 2;
}
int main() {
// 创建异步任务
AsyncTask task = async_operation(42, 1000);
// 获取future
std::future<int> future = task.get_future();
std::cout << "等待异步任务完成..." << std::endl;
// 等待结果
int result = future.get();
std::cout << "异步任务结果: " << result << std::endl;
// 输出: 异步任务结果: 84
return 0;
}

🚀 协程的高级特性

5.1 协程的状态管理

协程的状态由编译器自动管理,包括:

  • 局部变量的保存和恢复
  • 执行位置的跟踪
  • 异常的传播和处理

5.2 协程的挂起和恢复策略

协程的挂起行为由Promise类型的initial_suspend()final_suspend()方法控制:

  • std::suspend_always:总是挂起
  • std::suspend_never:从不挂起

5.3 协程的异常处理

协程中的异常会被捕获并通过Promise的set_exception()方法传播给调用者。 🛠️ 协程的应用场景

6.1 异步编程

协程非常适合异步编程模型,如:

  • 网络I/O操作
  • 文件I/O操作
  • 异步事件处理
  • 并发任务调度

6.2 生成器模式

用于生成序列值,如:

  • 无限序列生成
  • 惰性计算
  • 数据流处理

6.3 状态机实现

协程可以简化复杂状态机的实现,如:

  • 解析器(如JSON解析器)
  • 游戏AI
  • 工作流引擎

6.4 并发控制

用于实现高效的并发控制,如:

  • 轻量级线程池
  • 任务调度器
  • 协作式多任务 ⚖️ 协程的优缺点

7.1 优点

  1. 高性能:协程的创建和切换开销远小于线程
  2. 简化异步代码:将异步代码写得像同步代码一样清晰
  3. 更好的资源利用率:减少线程上下文切换的开销
  4. 更灵活的控制流:允许复杂的控制流模式
  5. 更好的可组合性:便于构建复杂的异步操作链

7.2 缺点

  1. 学习曲线陡峭:协程的概念和实现相对复杂
  2. 编译器支持要求高:需要C++20或更高版本的编译器
  3. 调试困难:协程的状态机转换使得调试变得复杂
  4. 内存管理复杂:需要正确管理协程的生命周期
  5. 标准库支持有限:C++20标准库对协程的支持相对基础 🌟 协程的最佳实践

8.1 设计良好的Promise和返回类型

  • 确保Promise类型实现了所有必要的接口
  • 设计直观易用的返回类型
  • 考虑异常安全

8.2 合理使用协程关键字

  • 明确区分co_awaitco_returnco_yield的使用场景
  • 避免过度使用协程,简单情况使用传统函数更合适

8.3 注意协程的生命周期管理

  • 确保协程最终被正确销毁
  • 避免悬垂的coroutine_handle

8.4 结合其他异步机制使用

  • std::futurestd::async结合使用
  • 与线程池和事件循环结合使用

8.5 考虑性能影响

  • 避免在性能关键路径上频繁创建和销毁协程
  • 合理设计协程的挂起和恢复策略 📋 总结

9.1 协程的核心要点

  1. 协程是特殊函数:可以暂停和恢复执行,保留执行状态
  2. 三个关键字co_awaitco_returnco_yield
  3. 编译器转换:协程被转换为复杂的状态机结构
  4. Promise和Awaitable:协程的核心组件,控制协程的行为
  5. 轻量级:创建和切换开销远小于线程
  6. 异步友好:简化异步编程模型

9.2 协程的发展前景

C++20协程是一个强大的特性,尽管目前标准库支持有限,但已经有许多第三方库提供了更高级的协程支持,如:

  • cppcoro:提供了丰富的协程工具和组件
  • libunifex:实验性的C++统一执行模型
  • boost.coroutine2:Boost库的协程实现 随着C++标准的不断发展,协程在C++生态系统中的应用将越来越广泛,成为异步编程和并发控制的重要工具。

9.3 学习建议

  1. 从简单示例开始:先理解基本概念和使用方法
  2. 深入学习实现原理:了解编译器如何转换协程
  3. 研究优秀实践:学习成熟库的设计模式
  4. 实际项目应用:在实际项目中尝试使用协程
  5. 关注标准发展:跟踪C++标准对协程的扩展和改进 通过深入学习和实践,开发者可以掌握协程这一强大工具,编写更高效、更清晰的异步和并发代码。

Thanks for reading!

50.C++ Coroutine

2026-01-23
2562 字 · 13 分钟

已复制链接

评论区

目录