C++ 协程是 C++20 引入的一项重大特性,它为编写异步 、并发 和惰性求值 的代码提供了强大的新工具。与传统的线程(Thread)相比,协程的优势在于用户态的上下文切换,这带来了极低的性能开销。
概念与优势
什么是协程?
协程是一种非抢占式 的、用户态 的程序控制流组件。它可以暂停 (co_yield 或 co_await)并在稍后从暂停点恢复执行。
- 与线程的区别:
- 线程 由操作系统内核调度,抢占式切换,切换开销大(涉及内核态/用户态切换、TLB 失效等)。
- 协程 由程序员在用户态协作调度,非抢占式切换,切换开销极小(仅涉及保存/恢复寄存器状态)。
- 优势:
- 高性能: 极低的上下文切换成本。
- 高并发: 单个线程可以运行成千上万个协程。
- 简化异步代码: 避免了传统异步编程中复杂的回调嵌套("Callback Hell"),使异步代码看起来像同步代码一样直观。
C++ 协程的关键操作符
C++20 引入了三个新的关键字,它们是协程机制的核心:
co_return: 用于从协程中返回值或结束协程,类似于return。co_yield: 用于暂停协程,并返回一个值给调用者(常用于实现生成器/迭代器)。co_await: 用于暂停 协程,等待一个可等待对象 (Awaitable) 的结果,并在结果就绪时恢复协程。这是实现异步编程的关键。
底层机制
C++ 协程并非一个具体的类型,而是一套定义协程行为的框架。一个协程的定义依赖于以下几个核心类型和定制点:
承诺类型 (Promise Type)
这是 C++ 协程机制的核心定制点 。它是一个由协程的返回类型(R in R func(...))推导或关联的类型。你需要在承诺类型中定义协程的生命周期和行为,包括:
get_return_object(): 在协程开始时调用,用于创建并返回给调用者(例如std::future或Task对象)。initial_suspend(): 决定协程在创建后是否立即暂停 。返回一个可等待对象。final_suspend(): 决定协程在co_return或执行完毕后是否暂停 。返回一个可等待对象。return_value(value)/return_void(): 处理co_return返回的值。yield_value(value): 处理co_yield的值。unhandled_exception(): 处理协程内部未捕获的异常。
协程句柄 (Coroutine Handle)
类型是 std::coroutine_handle<PromiseType>。它是恢复 协程执行的唯一凭证 。它封装了协程的状态和上下文,并提供了 resume()、destroy() 和 done() 等方法。
- 重要性: 协程句柄负责管理协程的生命周期和恢复操作。通常,它是隐藏在返回对象(如
Task或Generator)内部的。
协程状态 (Coroutine State)
这是一个堆分配(除非编译器优化)的内存块,用于存储:
- 承诺对象 (
PromiseType的实例)。 - 协程参数 的副本。
- 局部变量 中需要在暂停/恢复时保持状态的部分。
- 协程的恢复点地址(即当前暂停的位置)。
使用场景
异步编程 (Asynchronous Programming)
这是协程最主要的应用场景,通过 co_await 实现。
-
模式: 当遇到一个耗时操作(如 I/O、网络请求),协程使用
co_await暂停自身,释放底层线程去处理其他任务。当耗时操作完成时,一个调度器(Executor)会利用存储的协程句柄来恢复协程的执行。 -
效果: 极大地简化了基于
future/promise或回调的异步代码。例如,一个网络请求流程可以写成:C++Task<Data> fetch_data(URL url) { auto header = co_await network::get_header(url); auto body = co_await network::get_body(header); co_return Data{header, body}; }这段代码看起来是同步的,但实际上在
co_await处发生了非阻塞的暂停。
生成器 (Generators)
通过 co_yield 实现惰性求值的序列生成器。
-
模式: 当协程执行到
co_yield value时,它暂停 并将value返回给调用者。当下一次请求序列中的下一个元素时,协程从上次co_yield的位置恢复执行。 -
效果: 内存效率高(只计算当前需要的元素),代码简洁。例如:
C++Generator<int> range(int start, int end) { for (int i = start; i < end; ++i) { co_yield i; } }
惰性求值 (Lazy Evaluation)
通过 initial_suspend() 的设置,使协程在创建后立即暂停,只有当显式调用 resume() 时才开始执行。这常用于构建 Task 抽象,实现类似 std::lazy<T> 的效果。
实现一个 Awaitable 对象
co_await 关键字后面必须接一个可等待对象 (Awaitable)。一个可等待对象通常需要实现以下三个方法:
await_ready(): 检查结果是否已经就绪。- 返回
true:协程不暂停 ,直接执行await_resume()。 - 返回
false:协程暂停。
- 返回
await_suspend(std::coroutine_handle<> caller_handle): 在协程暂停时调用。- 核心逻辑: 在此函数中,可等待对象将
caller_handle存储起来(以便未来恢复),并启动异步操作。 - 返回值:
bool: 返回true表示协程成功暂停,控制权返回给调用者。返回false表示协程立即恢复。void: 协程成功暂停,控制权返回给调用者。std::coroutine_handle<>: 协程暂停,并立即恢复返回的这个句柄所指向的协程。
- 核心逻辑: 在此函数中,可等待对象将
await_resume(): 在协程恢复执行时调用,用于获取异步操作的结果(或抛出异常)。
示例
使用 co_yield 实现生成器 (Generator)
生成器是协程最直观的应用之一,用于惰性地生成一个序列,避免一次性在内存中存储所有结果。
c++
#include <iostream>
#include <coroutine>
#include <utility>
#include <exception>
// =======================================================
// 1. Generator 的核心结构体:承诺类型 (Promise Type)
// =======================================================
template <typename T>
struct Generator; // 前置声明
template <typename T>
struct GeneratorPromise {
T current_value; // 用于存储 co_yield 产生的值
// 必须定义:获取返回对象(将 Promise 关联到 Generator)
Generator<T> get_return_object() {
// 从当前 Promise 对象获取 Coroutine Handle
return Generator<T>{
std::coroutine_handle<GeneratorPromise<T>>::from_promise(*this)
};
}
// 必须定义:初始暂停------协程创建后立即暂停
// 这允许调用者在第一次迭代时才启动协程
std::suspend_always initial_suspend() { return {}; }
// 必须定义:最终暂停------协程执行完毕或 co_return 后的行为
// 暂停是为了让 Generator 对象在被销毁时,能安全地销毁协程帧
std::suspend_always final_suspend() noexcept { return {}; }
// 必须定义:处理 co_yield value 的定制点
std::suspend_always yield_value(T value) {
current_value = std::move(value);
return {}; // 暂停协程,将控制权交还给调用者
}
// 必须定义:处理 co_return 或执行完毕的定制点
void return_void() {}
// 必须定义:处理未捕获异常的定制点
void unhandled_exception() {
// 在实际生产代码中,通常会存储异常以便在 resume 时抛出
throw;
}
};
// =======================================================
// 2. Generator 的返回对象:支持范围循环的迭代器
// =======================================================
template <typename T>
struct Generator {
using promise_type = GeneratorPromise<T>;
using handle_type = std::coroutine_handle<promise_type>;
handle_type handle_;
// 构造函数
Generator(handle_type h) : handle_(h) {}
// 析构函数:重要!必须销毁协程句柄以释放内存
~Generator() { if (handle_) handle_.destroy(); }
// 禁用拷贝和赋值(协程句柄通常不可拷贝)
Generator(const Generator&) = delete;
Generator& operator=(const Generator&) = delete;
// 启用移动语义
Generator(Generator&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {}
Generator& operator=(Generator&& other) noexcept {
if (this != &other) {
if (handle_) handle_.destroy();
handle_ = std::exchange(other.handle_, nullptr);
}
return *this;
}
// --- 迭代器支持 (Iterator Support) ---
// 使得 Generator 可以用于 for 循环 (for (auto val : gen))
struct iterator {
handle_type handle_ = nullptr;
// 构造函数
iterator() = default;
explicit iterator(handle_type h) : handle_(h) {
// 在 for 循环开始时,迭代器会进行一次预取,
// 确保协程从 initial_suspend 恢复并产生第一个值
if (handle_ && !handle_.done()) {
handle_.resume();
}
}
// 不相等比较:用于判断是否到达末尾
bool operator!=(std::default_sentinel_t) const {
return handle_ && !handle_.done(); // 只要没有完成且句柄有效,就不是末尾
}
// 前置自增:恢复协程以产生下一个值
iterator& operator++() {
handle_.resume();
return *this;
}
// 解引用:获取当前 co_yield 的值
T operator*() const {
return handle_.promise().current_value;
}
};
// range-based for 循环的 begin()
iterator begin() {
return iterator{handle_};
}
// range-based for 循环的 end()
std::default_sentinel_t end() {
return {};
}
};
// =======================================================
// 3. 协程函数:使用 co_yield
// =======================================================
// 生成器函数:生成从 start 到 end (不含) 的整数序列
Generator<int> iota_generator(int start, int end) {
std::cout << "[Generator]: 协程启动...\n";
for (int i = start; i < end; ++i) {
std::cout << "[Generator]: co_yield " << i << "\n";
co_yield i; // 暂停,返回值,等待下一次 resume
}
std::cout << "[Generator]: 协程执行完毕。\n";
co_return; // 结束协程
}
// 另一个例子:斐波那契数列生成器
Generator<long long> fibonacci_generator(int count) {
long long a = 0, b = 1;
for (int i = 0; i < count; ++i) {
co_yield a;
long long next = a + b;
a = b;
b = next;
}
}
// =======================================================
// 4. 主函数演示
// =======================================================
int main() {
std::cout << "--- 演示 1: 简单的 iota 生成器 ---\n";
// 1. 创建 Generator 对象 (协程被创建,并在 initial_suspend 处暂停)
auto numbers_gen = iota_generator(10, 14);
// 2. 使用 range-based for 循环
std::cout << "\n[Main]: 开始 for 循环:\n";
for (int n : numbers_gen) {
std::cout << "[Main]: 接收到值: " << n << "\n";
}
std::cout << "[Main]: for 循环结束。\n\n";
// 3. 演示斐波那契数列
std::cout << "--- 演示 2: 斐波那契数列 (前 10 个) ---\n";
for (long long fib : fibonacci_generator(10)) {
std::cout << fib << " ";
}
std::cout << "\n";
return 0;
}
使用 co_await 实现异步任务 (Task/Future)
c++
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
#include <future>
#include <utility>
#include <exception>
// =======================================================
// 1. Task 的核心:承诺类型 (Promise Type)
// =======================================================
template <typename T>
struct Task; // 前置声明
template <typename T>
struct TaskPromise {
std::promise<T> result_promise; // 使用 std::promise 来存储最终结果或异常
// 必须定义:获取返回对象 (Task<T>)
Task<T> get_return_object() {
return Task<T>{
std::coroutine_handle<TaskPromise<T>>::from_promise(*this)
};
}
// 必须定义:初始暂停------我们选择立即执行协程,不暂停
std::suspend_never initial_suspend() { return {}; }
// 必须定义:最终暂停------协程执行完毕后暂停,等待 Task 对象销毁
std::suspend_always final_suspend() noexcept { return {}; }
// 处理 co_return value
void return_value(T value) {
result_promise.set_value(std::move(value));
}
// 处理 co_return; (如果 Task<void>)
void return_void() {
if constexpr (std::is_void_v<T>) {
result_promise.set_value();
} else {
// Task<T> 必须返回 T
}
}
// 处理未捕获异常
void unhandled_exception() {
result_promise.set_exception(std::current_exception());
}
};
// =======================================================
// 2. Task 的返回对象 (Task<T>)
// =======================================================
template <typename T>
struct Task {
using promise_type = TaskPromise<T>;
using handle_type = std::coroutine_handle<promise_type>;
handle_type handle_;
Task(handle_type h) : handle_(h) {}
~Task() { if (handle_) handle_.destroy(); }
// 移动语义
Task(Task&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {}
Task& operator=(Task&& other) noexcept {
if (this != &other) {
if (handle_) handle_.destroy();
handle_ = std::exchange(other.handle_, nullptr);
}
return *this;
}
// 禁用拷贝
Task(const Task&) = delete;
// 提供等待结果的 Future 接口
std::future<T> get_future() {
return handle_.promise().result_promise.get_future();
}
// 阻塞式获取结果(仅用于演示,实际异步不推荐)
T get() {
return get_future().get();
}
};
// 特化 Task<void> 的 promise,不需要 T value
template <>
struct TaskPromise<void> : TaskPromise<int> { // 继承是为了简化,实际应单独实现
void return_value(int value) {} // 忽略返回值
};
// =======================================================
// 3. Awaitable 对象:模拟异步操作
// =======================================================
template <typename T>
struct DelayedValue {
T value_;
int delay_ms_;
// 构造函数
DelayedValue(T val, int delay) : value_(std::move(val)), delay_ms_(delay) {}
// 1. await_ready: 检查是否立即就绪 (同步完成)
bool await_ready() const noexcept {
return delay_ms_ <= 0;
}
// 2. await_suspend: 协程暂停,启动异步操作
void await_suspend(std::coroutine_handle<> caller_handle) const {
std::cout << " [Awaitable]: 协程暂停,启动 " << delay_ms_ << "ms 延迟任务...\n";
// **关键点:在新线程中执行耗时操作,完成后恢复协程**
std::thread([h = caller_handle, ms = delay_ms_] {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
std::cout << " [Awaitable]: 延迟结束,恢复协程!\n";
h.resume(); // <--- 恢复调用方的协程
}).detach();
}
// 3. await_resume: 协程恢复后,返回异步操作的结果
T await_resume() const noexcept {
return value_;
}
};
// =======================================================
// 4. 协程函数:使用 co_await
// =======================================================
Task<int> compute_async_result() {
std::cout << "[Task]: 步骤 1: 任务开始。\n";
// 第一次 co_await:暂停协程,等待 1500ms
int result1 = co_await DelayedValue<int>(10, 1500);
std::cout << "[Task]: 步骤 2: 收到第一个异步结果: " << result1 << "\n";
// 第二次 co_await:暂停协程,等待 500ms
int result2 = co_await DelayedValue<int>(20, 500);
std::cout << "[Task]: 步骤 3: 收到第二个异步结果: " << result2 << "\n";
// 使用同步代码的方式组合异步结果
co_return result1 + result2;
}
// =======================================================
// 5. 主函数演示
// =======================================================
int main() {
std::cout << "--- 异步任务 (Task) 演示 ---\n";
// 启动协程任务 (协程立即执行到第一个 co_await 处暂停)
Task<int> my_task = compute_async_result();
// 主线程继续执行,不会被阻塞
std::cout << "[Main]: 主线程继续执行,等待 Task 结果...\n";
// 通过 get() 阻塞地等待最终结果
try {
int final_result = my_task.get();
std::cout << "[Main]: **最终结果:** " << final_result << "\n"; // 10 + 20 = 30
} catch (const std::exception& e) {
std::cerr << "[Main]: 任务发生异常: " << e.what() << "\n";
}
// 确保所有 detach 的线程有时间运行完毕
// (虽然 get() 已经阻塞到结果返回,但确保输出完整)
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "--- 演示结束 ---\n";
return 0;
}