C++20 引入了协程(Coroutines),协程是一种可以在执行过程中暂停和恢复的函数,允许在特定点挂起(suspend)执行,并将控制权交还给调用者,稍后可以从暂停点恢复执行。
应用场景
与传统的函数调用不同,协程可以在不丢失上下文的情况下暂停,适合以下场景:
1.异步编程:例如处理 I/O 操作(如网络请求、文件读写)时,避免阻塞线程。
2.生成器:逐步生成数据序列,适合处理大数据流或惰性求值。
3.协作式多任务:多个任务协作运行,共享线程资源。
关键字
C++ 的协程是通过编译器支持的底层机制实现的,主要依赖三个关键字:
1.co_await
:暂停协程并等待某个异步操作完成。
2.co_yield
:暂停协程并向调用者返回一个值(用于生成器)。
3.co_return
:结束协程并返回结果。
关键组件
C++ 协程的实现依赖于几个关键组件,这些组件共同定义了协程的行为:
1.Promise 对象(promise_type
):每个协程都有一个关联的 promise_type
,它定义了协程的行为和返回值。promise_type
是一个类,通常包含以下方法:
get_return_object()
:定义协程的返回值对象(通常是一个协程句柄或自定义类型)。
initial_suspend()
:决定协程在启动时是否立即挂起(返回 std::suspend_always
或 std::suspend_never
)。
final_suspend()
:决定协程在结束时是否挂起。
return_void()
或 return_value(T)
:处理协程的返回值。
yield_value(T)
:处理通过 co_yield
返回的值(生成器场景)。
unhandled_exception()
:处理协程中未捕获的异常。
2.协程句柄(std::coroutine_handle
):std::coroutine_handle
是一个模板类,用于管理协程的状态和生命周期。它可以:恢复协程(resume()
),检查协程是否完成(done()
),销毁协程(destroy()
)。
3.协程框架:协程的执行需要一个框架来管理暂停和恢复。C++20 标准库没有提供内置的协程调度器,因此开发者通常需要:手动管理协程句柄,使用第三方库(如 liburing
、boost::asio
)提供的事件循环或调度器。
4.暂停点:暂停点由 co_await
或 co_yield
定义:
co_await expr
:暂停协程,等待表达式 expr
(通常是一个等待器,awaitable
)完成。
co_yield expr
:暂停协程并向调用者返回一个值。
暂停时,协程的状态(包括局部变量)被保存在堆上,称为协程帧(coroutine frame)。
工作原理
C++ 协程的实现依赖编译器将协程函数转换为状态机(state machine)。其工作原理如下:
1.函数转换 :当编译器遇到 co_await
、co_yield
或 co_return
时,它将协程函数拆分为多个状态,每个状态对应一个暂停点或结束点。
2.协程帧分配:协程的局部变量和状态存储在堆上的协程帧中,确保暂停后状态不丢失。
3.状态机执行:协程在暂停点挂起时,返回控制权给调用者;恢复时,从暂停点继续执行。
4.生命周期管理 :协程帧的分配和销毁由 promise_type
和 std::coroutine_handle
管理。
示例
生成器示例(使用 co_yield
)
这个示例实现一个生成整数序列的协程:
cpp
#include <iostream>
#include <coroutine>
#include <optional>
// 生成器类型
template<typename T>
struct Generator {
struct promise_type {
std::optional<T> value;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
std::suspend_always yield_value(T val) {
value = val;
return {};
}
void unhandled_exception() {}
};
struct Iterator {
std::coroutine_handle<promise_type> coro;
bool done;
Iterator& operator++() {
if (!coro.done()) {
coro.resume();
done = coro.done();
}
return *this;
}
T operator*() const {
return *coro.promise().value;
}
bool operator!=(const Iterator& other) const {
return !done;
}
};
std::coroutine_handle<promise_type> coro;
Generator(std::coroutine_handle<promise_type> h) : coro(h) {}
~Generator() { if (coro) coro.destroy(); }
Iterator begin() {
coro.resume(); // 运行到第一个 yield
return {coro, coro.done()};
}
Iterator end() {
return {coro, true};
}
};
// 生成 1 到 n 的序列
Generator<int> generate_sequence(int n) {
for (int i = 1; i <= n; ++i) {
co_yield i;
}
}
int main() {
for (int val : generate_sequence(5)) {
std::cout << val << " "; // 输出: 1 2 3 4 5
}
std::cout << "\n";
return 0;
}
说明:Generator
是一个自定义协程类型,存储通过 co_yield
返回的值。promise_type
的 yield_value
方法将每次 co_yield
的值保存到 values
向量中。main
函数调用生成器并打印结果。
异步示例(使用 co_await
)
以下是一个模拟异步任务的协程,假设有一个简单的等待器:
cpp
#include <iostream>
#include <coroutine>
#include <chrono>
#include <thread>
// 模拟的等待器
struct Awaiter {
bool await_ready() { return false; } // 总是暂停
void await_suspend(std::coroutine_handle<> h) {
// 模拟异步操作(如 I/O)
std::thread([h]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
h.resume(); // 恢复协程
}).detach();
}
void await_resume() {}
};
// 异步任务
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
Task async_task(int id) {
std::cout << "Task " << id << " started\n";
co_await Awaiter{}; // 模拟异步等待
std::cout << "Task " << id << " completed\n";
}
int main() {
// 启动 5 个异步任务(避免 10000 个以简化输出)
std::vector<Task> tasks;
for (int i = 0; i < 5; ++i) {
tasks.push_back(async_task(i));
}
// 等待所有任务完成(简单模拟)
std::this_thread::sleep_for(std::chrono::milliseconds(200));
return 0;
}
说明:Awaiter
模拟一个异步操作(如网络 I/O),在 100ms 后恢复协程。co_await Awaiter{}
暂停协程,直到异步操作完成。main
启动多个异步任务,实际场景中需要事件循环来管理。
协程的优缺点
优点
1.高效性:协程在用户态管理暂停和恢复,避免了线程切换的开销。
2.灵活性:C++ 协程是底层机制,允许开发者自定义调度和行为。
3.异步编程简化:与回调或 std::future
相比,协程代码更直观,类似同步代码。
4.生成器支持:适合处理流式数据或惰性求值。
缺点
1.复杂性:需要手动定义 promise_type
和调度逻辑,学习曲线陡峭。
2.标准库支持不足:C++20 没有内置事件循环或调度器,需依赖第三方库。
3.内存管理:协程帧在堆上分配,可能增加内存开销。
4.调试困难:状态机转换和协程帧管理可能导致调试复杂。
性能优化
避免在协程中分配过多内存,合理设计暂停点。