-
协程 不是 C++函数, 而是一个 C++对象, 能 实现 挂起运行 和 恢复运行
原理是, 协程 的 主函数 运行 所依赖的 栈(保存了所有的栈变量), 能 保存到堆 和 从堆重新恢复到栈
当协程 挂起运行时, 就会 将所有的 栈变量 拷贝到 堆里
当协程 恢复运行时, 再重新 将变量 从 堆拷贝到栈里
整个过程 由 C++20 来支持(编译时? 运行时?)
所有 的整个 运行过程 与 操作系统的线程 是完全 独立和无关的
-
当 一个 C++函数 中 出现了 co_await, co_yield 或 co_return 时
就 成为了 一个 C++协程对象, 而不在是 C++函数了
-
关键字 co_await 后面 接 一个 "可等待对象"
语义是: 让 当前函数(准确的说 已经不是函数了, 而是一个协程对象) "暂停并等待" 后面接的 这个 "可等待对象" "执行完成"
"暂停并等待" 只是 阻塞的是 co_await 这条指令, 会导致 协程主函数 阻塞
"可等待对象" 的 "执行完成" 是一个状态, 而不是 CPU指令流
即 "可等待对象" 的状态 为 "ready" 或者 "suspend 后 resume"
"可等待对象" 它用于 告知 协程 ( 即 co_await 的地方 ) 如何 暂停 和 恢复
它 必须 实现 如下 三个 关键 函数
await_ready():
用于 告知 协程 能不能 拿到 "可等待对象" 的 返回结果
如果 返回 false, 则 co_await(即源协程) 会执行到 await_suspend
await_suspend(std::coroutine_handle<> handle):
用于 执行 挂起后 的操作, co_await(即源协程) 会一直阻塞 等等其完成
当然了, 这里也可以启线程, 如此 就不阻塞 co_await时 的 线程了
但是 逻辑上, 整个协程 是 处于 "挂起" 状态的
非常关键的是 参数 handle, 它 可以理解为 协程状态的遥控器
不论 源线程 是否阻塞, 此时 的 协程肯定 是阻塞了(即被挂起了)
如何恢复呢, 就需要调用 handle 的 resume 方法, 不论是在哪里(任何线程) 都能恢复 协程的执行
额外 await_suspend 可以有 一个 bool 型返回值
如果 返回 true, 则 认为是 正常的 挂起, 需要 将来 handle 的 resume 被调用 才行
await_suspend 如果 是 void 与 "返回 true" 的行为类似
如果 返回 false, 则 意味着 告诉 co_await(即源协程) 无需挂起, 直接恢复(有结果了)即可
相当于 在 await_suspend 内就 调用了 handle 的 resume 方法, 只是效率更高了
这个 bool 返回值的 用以 与 await_ready 的 返回值 正好 相反
await_resume():
当恢复时(即: 能 拿到结果时) 执行, 其返回值 就是 "可等待对象" 的 返回结果
-
协程(包含 co_* 的函数) 必须返回 一个 定义了 名称为 "promise_type" 的 "内部类" 的 类对象
promise_type 的核心用以 是 实现 协程调用者 与 协程的 交互
注: 这里的 "协程调用者" 可以是一个 普通的 C++ 函数
当 外部函数 调用 协程 时, 第一时间 并不是 执行 协程的 主函数,
而是 创建 一个 promise_type 对象
然后 立刻调用 其 get_return_object 方法 构造一个 协程函数 的返回类型对象
当 外部函数 需要 协程有 返回值时
- promise_type 需要 在 get_return_object() 函数里 构造 并返回 协程返回的 类型的对象
这里 可以为 promise_type对象 和 "协程返回的 类型的对象" 建立一些联系
- 然后 需要在 协程的 主函数 里 直接 使用 co_return 返回 外部函数 需要的 数据对象(按需, 任何类型都行)
当 co_return 被执行时, 会 触发 promise_type 的 return_value(...) 函数, 其参数 就是 co_return 返回的 对象
- 对于 外部函数(普通C++函数) 而言, 获取的返回值 仍然是 协程定义的 返回值类型 对象
可以 在 这个 类型 上 定义 一个成员函数 或者 字段 能使 外部 函数 拿到 其想要的值
-
这个 值的 传递 是可以做到的, 因为
-
这个值 最初 极可能 是在 "可等待对象" 中产生的
-
在 "可等待对象" 的 await_resume 方法里 返回给了 协程
即 co_await 执行后 拿到的 返回值
-
在 co_await 所在的 协程主函数里 使用 co_return 又能将 这个值 传递给 promise_type 的 return_value 这个函数的参数
-
而 promise_type 的 get_return_object 函数 在 构造 "整个协程 返回的 类对象" 时 有 建立 两者的联系
此时 就又机会 将 整个返回值 传递给 "整个 协程的 返回值类型对象"
- 需要 注意 "协程返回类型的对象" 会在 协程主函数 执行前 就 被构造出来了
此时 真正的 返回值(协程执行的成果) 肯定无法产生,
所以 此时 只能先 建立 "协程返回类型的对象" 与 "promise_type对象" 的联系,
"协程返回类型的对象" 是会给到 外部函数的,
等待将来 能从
-
关于 initial_suspend
首先 initial_suspend 的调用时机 是在 外部函数 调 用协程时 触发的 promise_type 构造函数 以及 get_return_object 执行完之后 所要立即 调用的 第三个 固定函数
如果 其 返回 suspend_never 会让 协程体 立刻开始执行
如果 其 返回 suspend_always 会让 协程立刻 挂起, 不立刻 执行 协程体
此时 的 协程 将立刻 处于 挂起状态
需要 某个地方 调用 coroutine_handle 的 resume 方法 才能 恢复 协程体的执行
coroutine_handle 对象 除了 能在 Awaitable 的 await_suspend 中拿到外, 还能 随时由 promise_type 对象构造出来
例如 可以在 get_return_object 函数里 构造 协程返回对象时 构造它, 并作为 参数 给到 协程返回对象
std::coroutine_handle<promise_type>::from_promise(*this)
-
关于 final_suspend
首先 final_suspend 的调用时机 时在 协程体 执行完 co_return 之后, 但在 协程对象 销毁前 被调用的
如果 其 返回 suspend_never 则 协程 会立刻销毁, 释放资源
如果 其 返回 suspend_always 则 协程不会销毁,
但也不能再 调用 coroutine_handle 的 resume 了
只能 调用 coroutine_handle 的 destroy 来触发 协程销毁
用处, 主要是为了让 外部 感知协程结束, 并决定 其销毁时机
cpp
#include<coroutine>
#include<iostream>
#include<memory>
struct Awaitable {
bool await_ready() {
return false;
}
void await_suspend(std::coroutine_handle<> handle) {
// handle.resume();
}
int await_resume() {
return 42;
}
};
struct CoTask {
struct promise_type {
promise_type() {
m_value = std::make_shared<int>();
}
CoTask get_return_object() {
return CoTask(
std::coroutine_handle<promise_type>::from_promise(*this),
m_value
);
}
std::suspend_always initial_suspend() {
return std::suspend_always{};
}
std::suspend_always final_suspend() noexcept {
return std::suspend_always{};
}
void unhandled_exception() {}
// void return_void() {}
void return_value(int v) {
*m_value = v;
}
std::shared_ptr<int> m_value;
};
explicit CoTask(std::coroutine_handle<promise_type> handle, std::shared_ptr<int> value) {
m_handle = handle; m_value = value;
}
std::coroutine_handle<promise_type> m_handle;
std::shared_ptr<int> m_value;
};
CoTask run() {
Awaitable aw;
auto result = co_await aw;
co_return result;
}
int main() {
auto t = run();
std::cout << *t.m_value << std::endl;
for (;;) { auto c = getchar(); if (c == 'x') { break; }
if (c == 'a') {
t.m_handle.resume();
}
}
t.m_handle.destroy();
std::cout << *t.m_value << std::endl;
return 0;
};