参考:
https://www.cnblogs.com/blizzard8204/p/17563217.html
https://www.bennyhuo.com/2022/03/09/cpp-coroutines-01-intro/
本文不完整, 更新中
1 基本概念
什么是协程?
- C++ 20 的协程是一个特殊函数, 具有挂起和恢复的能力. (可以不一次性执行)
- 协程可用于异步编程, 提供了一种更轻量级的并发机制
- 使用了
co_await
、co_yield
和co_return
的函数都是协程 - 协程的返回类型必须满足条件: [[#协程的返回类型]]
c++20协程的缺陷
当前协程的实现非常复杂, 目前只提供了一些语法糖之类的东西, 需要根据很多规则编写代码, 最后由编译器依靠这些代码生成更复杂的代码. 就像 QT 框架那样
因此当前协程的范式是不完善的
协程是如何实现的
协程会在开始执行时的第一步就使用 operator new
来开辟一块内存来存放信息
包含如下内容
- 协程传入的参数
- 返回的
promise_type
类型对象 (也就是说, 返回对象一开始就创建了) - 协程的其他状态 (比如挂起点)
因此当需要暂停时, 那些状态被保存着, 等待恢复时使用.
协程执行完成 或 被销毁之后,协程的状态也被销毁释放
无栈协程和有栈协程
有栈:
- 为每一个协程创建一个独立的内存栈进行上下文的保存和函数调用
- boost 实现了有栈协程
无栈: - 一开始就会在堆上保存所有的协程函数的“临时变量”以及调用参数等上下文信息
- 从协程函数里切换出来的时候,因为大多数东西都是保存在堆上的,所以切换动作可以很短很快
- 现代 c++20使用的是无栈协程
每次创建协程时,编译器都会自动完成堆分配。
协程的返回类型要求
[[#promise_type 类型]]
- 协程的返回类型
Result
必须能够匹配__coroutine_traits_impl<Result>
的模板参数要求. - 而
__coroutine_traits_impl<Result>
要求其 返回类型Result
必须含有promise_type
简单来说,就是返回值类型 `Result` 含有 `Result::promise_type`
它可以是嵌套类, 或者使用 `using` 引入了 `promise_type` 类型.
`__coroutine_traits_impl` 是什么?
- 它是 `coroutine_traits` 的实现, 也是它的基类
- 它是一个空类型体, 只是用于检查 返回结果是否含有内部类型 `promise_type`
协程的执行流程
- 使用
operator new
分配协程返回类型对象. (该运算可以重载) - 将协程函数实参复制到协程状态对象中, 复制时可以按值传递或按引用传递
- 构造
return_type::promise_type
对象- 其中
return_type
是协程返回类型 - 构造
promise_type
对象时, 若它有一个构造函数形参和协程的形参一致, 则会调用那个构造函数, 并传入所有协程的实参; 否则调用默认构造器
- 其中
- 调用
promise_type::get_return_object()
, 并将返回值保留在局部变量中, 当协程挂起时, 该值将返回给调用点, - 调用
promise_type.initial_suspend()
- 然后对返回值进行
co_await
运算. - 如果返回值满足挂起条件, 则协程一开始就被挂起
- 否则进入下一步, 执行协程体
- 然后对返回值进行
- 执行协程体, 直到遇到
co_await
、co_yield
和co_return
流程图
Program with c++ 20
![[Pasted image 20241229210902.png]]
1.3 co_await
co_await 运算符和执行流程
- 用于挂起协程并等待某个异步操作完成
- 必须和 [[#awaitable 类型的概念|awaitable类型]]一起使用
co_await awaitable_obj;
如果使用默认的 co_await
运算, 那么将自动执行:
- 调用
awaitable_obj
的 [[#await_ready()]]- 如果返回
false
则表示协程未准备好, 协程将被挂起 - 如果返回
true
则直接执行协程体
- 如果返回
- 如果被挂起, 则执行 [[#await_suspend()]], 随后控制权交给调用协程处的程序.
- 如果在挂起后被恢复, 则 进入 [[#await_resume()]]
await_resume()
的返回值作为co_await
表达式的结果- 这个返回值是协程体需要的
- 协程恢复完成, 继续执行
co_await
之后的协程体.
co_await 执行的例子
在调试模式下, 单步执行该程序, 可清楚地看到 co_await
的流程和作用
struct Myawaiter {
bool await_ready() { //5
std::cerr << "await_ready\n";
return false;
}
void await_suspend(std::coroutine_handle<> h) { //6
std::cerr << "await_suspend\n";
}
int await_resume() { //8
std::cerr << "await_resume\n";
return 44;
}
};
struct promise_type;
struct RT {
public:
using promise_type = ::promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type _handle; //句柄成员
handle_type get_handle() { return _handle; }
~RT() {
}
};
struct promise_type {
RT get_return_object() { //1
std::cerr << "get_return_object\n";
return RT{._handle = std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() { //2
std::cerr << "initial_suspend\n";
return {};
}
std::suspend_never final_suspend() noexcept { //11
std::cerr << "final_suspend\n";
return {};
}
void return_void() { //10
std::cerr << "return_void\n";
}
void unhandled_exception() { std::terminate(); }
};
RT cofun() {
co_await Myawaiter{}; //4
co_return; //9
}
int main() {
RT r = cofun();
RT::handle_type h = r.get_handle();
h.resume(); //3
h.resume(); //7
return 0;
}
/* 打印结果
get_return_object
initial_suspend
await_ready
await_suspend
await_resume
return_void
final_suspend
*/
1.4 co_return
co_return 作用
co_return
可以将一个值返回给协程的调用者。 这个值可以是任何类型,包括基本类型、自定义类型、甚至是void
。co_return
语句会终止当前协程的执行,并调用promise_type::final_suspend()
, 并将控制权返回给调用者。- 最后协程将结束了.
co_return 流程
- 当协程执行到
co_return
语句时,它会先计算返回值 (如果有的话), 计算返回值过程如下:- 调用
promise::return_value()
:co_return
会调用与协程关联的promise_type
对象的return_value()
成员函数,并将计算得到的返回值传递给它。promise::return_value()
函数负责处理返回值,例如将其存储在promise
对象的成员变量中,或者进行其他自定义操作。- 如果
promise
对象没有定义return_value()
函数,或者co_return
语句没有返回值(例如co_return;
),则不会调用return_value()
函数。
- 调用
- 调用
promise::final_suspend()
- 协程的控制权返回给调用者
- 调用者可以通过
std::coroutine_handle::promise()
函数获取promise
对象,并访问其中存储的返回值。
- 调用者可以通过
co_return 执行的例子
在调试模式下, 单步执行该程序, 可清楚地看到 co_await
的流程和作用
struct Myawaiter {
bool await_ready() {
std::cerr << "await_ready\n";
return false;
}
void await_suspend(std::coroutine_handle<> h) { std::cerr << "await_suspend\n"; }
int await_resume() {
std::cerr << "await_resume\n";
return 44;
}
};
struct promise_type;
struct RT {
public:
using promise_type = ::promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type _handle; //句柄成员
handle_type get_handle() { return _handle; }
RT(std::coroutine_handle<promise_type> h) : _handle(h) {
std::cerr << "RT 构造函数\n";
}
~RT() {
std::cerr << "RT 析构函数\n";
}
int get_val();
};
struct promise_type {
int value{};
RT get_return_object() {
std::cerr << "get_return_object()\n";
return RT{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
std::cerr << "initial_suspend()\n";
return {};
}
std::suspend_always final_suspend() noexcept {
std::cerr << "final_suspend()\n";
return {};
}
void return_value(int x) {
std::cerr << "return_value()\n";
}
void unhandled_exception() { std::terminate(); }
promise_type() { std::cerr << "promise_type 构造函数()\n"; }
~promise_type() { std::cerr << "promise_type 析构函数()\n";
}
};
int RT::get_val() { return _handle.promise().value; }
RT cofun() {
std::cerr << "----------------------------------开始co_await...\n";
co_await Myawaiter{};
std::cerr << "----------------------------------结束co_await...\n";
std::cerr << "----------------------------------开始co_return...\n";
co_return 1;
std::cerr << "----------------------------------结束co_return...\n"; // 不会执行
}
int main() {
RT r = cofun();
RT::handle_type h = r.get_handle();
h.resume();
h.resume();
std::cerr << "协程设置的值为: " << r.get_val() << "\n";
h.destroy();
return 0;
}
/* 输出结果
promise_type 构造函数()
get_return_object()
RT 构造函数
initial_suspend()
----------------------------------开始co_await...
await_ready
await_suspend
await_resume
----------------------------------结束co_await...
----------------------------------开始co_return...
return_value()
final_suspend()
协程设置的值为: 0
promise_type 析构函数()
RT 析构函数
*/
1.5 co_yield
co_yield 流程
- 当协程执行到
co_yield expression;
语句时,会发生以下事情:expression
被计算,其结果将作为返回值, 返回给调用者- 控制权返回给协程的调用者。
- 协程的执行状态被保存,包括局部变量, 寄存器值等。
- 返回值给调用者: 协程的调用者会接收到
co_yield
表达式的结果。promise_type
中的成员变量来保存结果- 调用者可以通过一些接口来查询该结果.
- 恢复协程执行:当调用者希望继续执行协程时,可以调用
std::coroutine_handle<promise_type>::resume()
函数。- 此时,协程会从之前
co_yield
语句中断的地方恢复执行。 - 协程可以使用
co_await
关键字等待异步操作完成,也可以使用co_return
返回最终结果。
- 此时,协程会从之前
todo
也是一个 C++ 协程中的关键字,
用于暂停协程的执行并返回值给调用者。
每次调用 co_yield
都会产生一个挂起点, 并调用函数 promise_type::yield_value()
与 co_yield
相关的函数实际上是在 promise
对象中定义的,用于处理 co_yield
的行为。 主要的函数有:
-
yield_value()
:- 作用: 处理
co_yield
表达式的值。 - 参数:
co_yield
表达式的值会作为参数传递给yield_value()
函数。 - 返回值:
yield_value()
函数的返回值没有特殊意义,因为协程在co_yield
处已经暂停了。 - 示例:
- 作用: 处理
struct MyPromise {
// ...
std::suspend_always yield_value(int value) {
// 处理 co_yield 传递的值
currentValue = value;
std::cout << "Yielded value: " << value << std::endl;
return {}; // 返回 std::suspend_always 表示挂起协程
}
// ...
};
await_transform()
:- 作用:
await_transform()
本身不是专门为co_yield
服务的,但它可以与co_yield
配合使用,对co_yield
返回的对象进行自定义处理。 - 参数:
co_yield
表达式的值会作为参数传递给await_transform()
函数。 - 返回值:
await_transform()
函数的返回值决定了协程的行为:- 返回一个 awaitable 对象:协程会等待该 awaitable 对象完成后再继续执行。
- 返回
std::suspend_always
:协程会立即挂起。 - 返回
std::suspend_never
:协程会继续执行,不会挂起。
- 示例:
- 作用:
struct MyPromise {
// ...
std::suspend_always await_transform(int value) {
// 处理 co_yield 传递的值
std::cout << "Custom processing: " << value << std::endl;
return {}; // 挂起协程
}
// ...
};
总结:
co_yield
不是函数,而是一个用于暂停协程并返回值的关键字。yield_value()
函数用于处理co_yield
表达式的值。await_transform()
函数可以与co_yield
配合使用,对co_yield
返回的对象进行自定义处理。
例子
#include <iostream>
#include <coroutine>
// 定义 promise 类型,用于自定义协程的行为
struct GeneratorPromise {
int current_value;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
// 用于获取协程返回的对象
GeneratorPromise& get_return_object() { return *this; }
// 用于处理 co_yield 表达式
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
};
// 定义协程类型
struct Generator {
using promise_type = GeneratorPromise;
coroutine_handle<promise_type> handle;
Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
// 获取 co_yield 返回的值
int next_value() { return handle.promise().current_value; }
};
// 定义一个生成从 0 到 max 的整数序列的协程
Generator generate_numbers(int max) {
for (int i = 0; i <= max; ++i) {
co_yield i;
}
}
int main() {
// 创建协程
auto generator = generate_numbers(5);
while (generator.handle()) { //当协程句柄有效时, 则可以继续
std::cout << generator.next_value() << " ";
generator.handle().resume(); //继续协程
}
std::cout << std::endl;
return 0;
}
附录: 一些在协程中的重要类型
总结
如何在外部获取协程句柄?
如何在外部获取和协程关联的 promis_type 对象?
如果在外部获得协程的返回值?
awaiter 类型和它的成员函数
awaiter 类型
等待器类型可被用于 co_await
运算.
必须含有三个可见的成员函数:
await_ready
await_suspend
await_resume
当对一个 awaiter
类型进行 co_await
运算时, 将依次发生如下:
- 调用
await_ready()
, 根据返回值决定是否挂起- 返回
true
, 不挂起, 则继续执行协程体 - 返回
false
, 挂起, 则执行await_suspend()
, 此时协程体本身被阻塞
- 返回
- 当进入
await_suspend
后, 可以在适当的时候进入await_resume
, 继续执行协程体
await_ready() 成员
- 它返回
bool
值, 用于检查并判断是否需要挂起协程.- 返回
true
:表示协程可以直接继续执行 (已经准备好了),不需要挂起 - 返回
false
:表示协程需要挂起, 一旦挂起, 将进入await_suspend
- 返回
await_suspend() 成员
- 当
await_ready
返回false
导致协程被挂起时, 将调用这个函数 - 它接收一个 [[#std coroutine_handle 类型]] 的参数
- 这个参数实际上是当前协程的句柄,它指向了这个协程的执行上下文.
- 它的实参自动传递, 并不需要手动传递
await_suspend
的返回类型有多种
- 返回
void
表示当前协程挂起之后, 将执行权还给当初调用点 - 返回
true
,表示当前协程挂起之后, 将恢复当前协程的函数 - 返回
false
,则恢复执行当前协程- 注意此时不同于 返回
true
的情形,此时协程已经挂起,await_suspend
返回false
相当于挂起又立即恢复
- 注意此时不同于 返回
- 返回其他协程的
coroutine_handle
对象,这时候返回的coroutine_handle
对应的协程被恢复执行 - 抛出异常,此时当前协程恢复执行,并在当前协程当中抛出异常
例子
class awaiter{
public:
await_ready(){return false;} //需要挂起
void await_suspend(coroutine_handle<> h){ //传入句柄
}
constexpr bool await_suspend(coroutine_handle<> h){
std::cerr<<"await_suspend\n";
return false; //
}
}
struct ReturnType;
ReturnType mycorotine(){
}
await_resume() 成员
- 当协程恢复执行时调用
- 它的返回值作为
co_await
表达式的结果
suspend_never 和 suspend_alaways 类型
源码
struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
struct suspend_always{
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
suspend_never
的 await_ready
总是返回 true
, 这将总是挂起协程.
这里的 coroutine_handle<>
实际上是类型 coroutine_handle<void>
例子
struct promise_type{
//...
std::suspend_never initial_suspend(){
std::<<"不会挂起的\n";
return {};
}
};
awaitable 类型
awaitable 和 awaiter 的联系
- 可以进行
co_await
运算的类型 都称为awaitable
类型 awaitable
类型包含了awaiter
类型- 也包含那些通过 重载
co_await
运算, 从而可进行co_await
运算的类型 - 或者可以一步 隐式转换为 可进行
co_await
运算的类型
`awaitable` 是一个概念, 是一种接口或约定
`awaiter` 这是 `awaitable` 的具体实现
例子
class Awaiter_String: public std::string{
using std::string::string;
};
class Awaitable_String : public std::string{
}
std::coroutine_handle 类型
coroutine_handle 类型介绍
- 是一个类型模板, 定义在
corotine
头文件中 - 模板实参一般是 [[#promise_type 类型]], 或者为空
- 通过该类型的对象, 可以手动管理协程的生命周期
template <typename _Promise>
struct coroutine_handle{
//...
};
coroutine_handle 的常见成员函数
通过句柄的一些成员函数, 可以控制协程的状态.
源码
constexpr explicit operator bool() const noexcept{
return bool(_M_fr_ptr);
}
bool done() const noexcept { return __builtin_coro_done(_M_fr_ptr); }
void operator()() const { resume(); }
void resume() const { __builtin_coro_resume(_M_fr_ptr); }
void destroy() const { __builtin_coro_destroy(_M_fr_ptr); }
解释
resume()
:用于恢复协程的执行。如果协程已经完成或销毁,则行为未定义。destroy()
:销毁协程,这会清理协程的状态并释放资源。应当在协程不再需要时调用。done()
:返回一个布尔值,指示协程是否已经完成operator()
实际上也是调用resume
恢复协程
`__builtin_coro_destroy`, `__builtin_coro_resume` 等这些都是编译器内置的函数, 没有源码, 不具备可移植性
获取协程的句柄
由于协程在开始时, 就构造了返回对象, 以及 promise_type
对象.
因此这个 promise_type
对象就和协程对应. 可以从 promise_type
定位协程.
那么理应从 promise_type
对象可以获取 协程句柄.
根据上面思想, 协程库的 coroutine_handle
有如下静态函数 from_promise
只要传入一个 promise_type
对象, 该函数可以构造并返回协程的句柄.
//源码
template <typename _Promise>
struct coroutine_handle {
//...
static coroutine_handle from_promise(_Promise &__p) {
coroutine_handle tmp;
tmp._M_fr_ptr =
__builtin_coro_promise(
(char *)&__p,
__alignof(_Promise),
true
);
return tmp;
}
}
从句柄获取协程的 promise_type 对象
使用 coroutine_handle
的 promise()
成员, 可以获取与协程句柄对应的 promie_type
对象.
//源码
template <typname _Promise>
struct coroutine_handle{
_Promise& promise() const {
return *static_cast<_Promise*>(
__builtin_coro_promise(this->__handle_,
alignof(_Promise),
false
)
);
}
};
promise_type 类型
- 协程的返回类型为
RT
,则RT
内必须含有类型RT::promise_type
promise_type
是自定义类型.- 协程有时候不必直接返回一个对象 (不必写一个
return
语句)- 因为协程的返回对象在协程开始时 已经构建好了
- 即使不做任何返回, 协程的状态区域也保存着返回值.
- 在
promise_type
可以定义一个get_return_object()
成员, 它可以构造返回对象.
struct myPromise{ //在返回类型外部定义 promise_type
//...
};
struct Return_Type{
using promise_type = myPromise;
friend promise_type;
//...
};
get_return_object()
- 这个成员函数是必须定义的,它负责创建协程的返回值
RT
类型对象 - 它的返回类型必须是 协程返回类型
RT
- 在协程一开始时, 它就会调用
get_return_object()
, 构造这个对象. 并返回给协程的调用点
例子
struct RT{
struct promise_type{
RT get_return_object(){//...
}
//...
}
}
RT cofunc(){ //协程函数
//
}
int main(){
RT r = cofunc(); // 协程一开始就构建好了这个对象r, 这个对象一般包含着协程句柄信息.
}
initial_suspend()
当协程一开始时, 实际将发生如下调用
co_await return_type::promise_type.initial_suspend();
initial_suspend()
函数返回一个 [[#awaitable 类型]] 的对象,- 返回值一般是
std::suspend_never
和std::suspend_always
- 返回
std::suspend_never
表示不挂起 - 返回
std::suspend_always
或自定义的 awaiter 则表示挂起
- 返回
- 返回值一般是
- 然后这个对象参与 [[#co_await 流程]] 运算
`suspend_never` 和 `suspend_always` 是什么?
- 它们都是 awaiter 类型, 参看下面源码
- `suspend_never`中 ,`await_ready`函数返回`true`, 表示准备好了,不会挂起
final_suspend()
这个成员函数在协程执行到最后(即遇到 co_return
或函数结束)时被调用
它的返回值决定协程是否在结束前挂起.
struct final_awaiter{
bool await_ready(){ return false;}
void await_suspend(coroutine_traits<> h){
h.destroy();
}
void await_resume(){}
};
struct promise_type{
//...
final_awaiter final_suspend(){
return {};
}
};
unhandled_exception()
当协程内部抛出未捕获的异常时,会调用这个函数
return_void() 和 return_value()
协程的返回类型
在下文中, 我们使用 Return_type
或者 RT 指代返回类型
coroutine_traits
类型 和 协程返回类型要求
源码
// C:\msys64\mingw64\include\c++\14.2.0\coroutine
template <typename _Result, typename... _ArgTypes>
struct coroutine_traits;
template <typename _Result, typename = void>
struct __coroutine_traits_impl {};
template <typename _Result>
// 用requires子句和表达式来约束_Result类型, 必须含有 promise_type
requires requires { typename _Result::promise_type; }
struct __coroutine_traits_impl<_Result, void>{
using promise_type = typename _Result::promise_type;
};
template <typename _Result, typename... _ArgTypes>
struct coroutine_traits : __coroutine_traits_impl<_Result> {};
coroutine_traits
从给定的协程返回类型_Result
中提取出promise_type
- 它只用于检查协程体的 返回类型 是否合格
- 编译器会根据协程的返回类型
_Result
自动推导出该类型的promise_type
,并使用它来进行协程的管理