C++20引入了协程(Coroutines)特性,它是一种特殊的函数,可以在执行过程中暂停,并在后续某个时刻恢复执行,而不会像普通函数那样一旦返回就彻底结束。协程非常适合处理异步操作(如网络IO、文件IO)、生成器(产生序列值)、状态机等场景,能够以同步的代码风格编写异步逻辑,避免“回调地狱”。
一、协程的基本概念
与普通函数相比,协程有三个核心特性:
- 可暂停:执行到特定点(
co_await
、co_yield
)时暂停,释放CPU控制权 - 可恢复:暂停后可通过外部触发继续执行
- 状态保留:暂停时的局部变量状态会被保存,恢复时继续使用
二、协程的识别与核心组件
1. 如何识别协程函数?
一个函数如果包含以下关键字之一,即为协程函数:
co_await
:暂停等待某个操作完成co_yield
:产生一个值并暂停(类似迭代器的yield
)co_return
:返回结果并结束协程
2. 核心组件
C++协程的实现依赖三个关键组件(由编译器和用户/库共同管理):
- 承诺对象(Promise Object):协程的“状态管理者”,负责创建返回值、处理暂停/恢复逻辑,由协程函数通过
promise_type
定义。 - 协程句柄(
std::coroutine_handle
):用于外部控制协程(恢复、销毁)的轻量级指针。 - 等待器(Awaiter):
co_await
后面的对象,定义了“等待”的行为(是否暂停、如何恢复)。
三、协程的生命周期
启动:调用协程函数时,编译器会:
- 分配协程帧(存储局部变量、状态等)
- 创建承诺对象(
promise_type
实例) - 执行协程函数体,直到遇到第一个暂停点(
co_await
/co_yield
)
暂停:遇到
co_await expr
时:- 计算
expr
得到一个等待器(Awaiter) - 调用等待器的
await_suspend
方法:若返回false
则不暂停,继续执行;若返回true
或协程句柄,则暂停,返回控制权给调用者。
- 计算
恢复:通过协程句柄的
resume()
方法,协程从暂停点继续执行。结束:执行到
co_return
或函数末尾时:- 调用承诺对象的
return_value
(或return_void
)方法 - 协程进入完成状态,可通过承诺对象获取结果
- 调用承诺对象的
四、关键关键字详解
1. co_await
:等待异步操作
co_await
用于暂停协程,等待某个异步操作完成(如网络请求返回)。语法:
co_await awaitable; // awaitable是一个“可等待对象”(实现了等待器接口)
等待器(Awaiter)需要实现三个方法:
bool await_ready()
:返回true
表示操作已完成,无需暂停void await_suspend(std::coroutine_handle<> handle)
:暂停时调用,通常会保存协程句柄以便后续恢复T await_resume()
:恢复时调用,返回等待结果(T
为结果类型)
2. co_yield
:产生序列值
co_yield
用于生成一个值,然后暂停协程,类似迭代器的“产生-暂停”逻辑。语法:
co_yield value; // 等价于 co_await promise.yield_value(value)
常用于实现生成器(如无限序列、懒加载集合)。
3. co_return
:返回结果并结束
co_return
用于返回最终结果并终止协程,语法:
co_return expression; // 结果会通过承诺对象传递给调用者
五、示例:从简单到实用
1. 基础协程:返回一个值
#include <coroutine>
#include <iostream>
#include <stdexcept>
// 定义协程的返回类型(需要包含promise_type)
struct MyResult {
// 承诺对象:协程内部状态管理者
struct promise_type {
int value; // 存储协程返回值
// 协程启动时调用,返回MyResult对象
MyResult get_return_object() {
return {std::coroutine_handle<promise_type>::from_promise(*this)};
}
// 初始暂停:返回std::suspend_never表示不暂停,立即执行
std::suspend_never initial_suspend() noexcept { return {}; }
// 最终暂停:返回std::suspend_never表示协程结束后自动销毁
std::suspend_never final_suspend() noexcept { return {}; }
// 处理co_return:保存返回值
void return_value(int v) { value = v; }
// 处理异常
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> handle; // 协程句柄
};
// 协程函数:返回MyResult
MyResult my_coroutine(int a, int b) {
co_return a + b; // 使用co_return返回结果
}
int main() {
auto result = my_coroutine(3, 5);
std::cout << "Result: " << result.handle.promise().value << std::endl; // 输出8
result.handle.destroy(); // 手动销毁协程帧(若final_suspend返回suspend_always)
return 0;
}
2. 生成器:用co_yield
产生序列
#include <coroutine>
#include <iostream>
#include <iterator>
// 生成器:产生int序列
struct Generator {
struct promise_type {
int current_value;
Generator get_return_object() {
return {std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; } // 初始暂停,等待resume
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
// 处理co_yield:保存值并暂停
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
};
// 实现迭代器接口,方便用for循环遍历
struct iterator {
std::coroutine_handle<promise_type> handle;
bool operator!=(const iterator&) const { return !handle.done(); }
void operator++() { handle.resume(); }
int operator*() const { return handle.promise().current_value; }
};
iterator begin() { handle.resume(); return {handle}; }
iterator end() { return {nullptr}; }
std::coroutine_handle<promise_type> handle;
};
// 生成1~n的平方数
Generator squares(int n) {
for (int i = 1; i <= n; ++i) {
co_yield i * i; // 产生平方数并暂停
}
}
int main() {
for (int v : squares(5)) { // 用for循环遍历生成器
std::cout << v << " "; // 输出:1 4 9 16 25
}
return 0;
}
3. 异步操作:用co_await
等待
#include <coroutine>
#include <iostream>
#include <thread>
#include <chrono>
// 模拟异步IO操作的等待器
struct AsyncOperation {
bool done = false;
int result;
// 检查操作是否完成(初始为false,需要暂停)
bool await_ready() const noexcept { return done; }
// 暂停时:启动异步操作,完成后恢复协程
void await_suspend(std::coroutine_handle<> handle) {
std::thread([this, handle]() {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟IO耗时
result = 42; // 异步操作结果
done = true;
handle.resume(); // 操作完成,恢复协程
}).detach();
}
// 恢复时返回结果
int await_resume() const noexcept { return result; }
};
// 协程函数:等待异步操作
struct AsyncResult {
struct promise_type {
int value;
AsyncResult get_return_object() { return {std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(int v) { value = v; }
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> handle;
};
AsyncResult async_task() {
std::cout << "等待异步操作..." << std::endl;
int result = co_await AsyncOperation(); // 等待异步操作完成
std::cout << "异步操作结果:" << result << std::endl; // 输出42
co_return result;
}
int main() {
auto task = async_task();
// 等待异步操作完成(实际中可能需要更复杂的同步)
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
六、协程的优势与适用场景
优势:
- 简化异步代码:以同步的线性风格编写异步逻辑,避免多层回调嵌套(“回调地狱”)。
- 轻量级:协程暂停/恢复在用户态完成,无需操作系统介入,上下文切换成本远低于线程。
- 状态保留:暂停时自动保存局部变量状态,无需手动管理(对比状态机的繁琐)。
适用场景:
- 异步IO:网络请求、文件读写等IO密集型操作。
- 生成器:懒加载序列(如无限数列、大数据流分页)。
- 状态机:复杂流程的状态管理(如游戏AI、协议解析)。
- 并发任务调度:在单线程内调度多个“逻辑线程”。
七、注意事项
- 标准库支持有限:C++标准仅定义了协程的基础机制(如
std::coroutine_handle
),具体异步操作(如网络、定时器)需要依赖第三方库(如libunifex、Boost.Coroutine2)或操作系统API封装。 - 内存管理:协程帧(存储状态的内存)需要手动管理(通过
coroutine_handle::destroy()
),否则可能内存泄漏。 - 调试复杂度:协程的暂停/恢复逻辑可能增加调试难度,需要支持协程的调试工具。
- 学习曲线:涉及承诺对象、等待器等概念,初期理解成本较高。
C++协程是现代C++异步编程的重要里程碑,虽然初期使用复杂,但能显著提升异步代码的可读性和效率。随着库生态的完善(如C++23/26可能增强标准库支持),协程将成为处理并发任务的主流方式。