C++ Coroutine 协程
类似于 Rust 的协程实现,C++也是需要添加自定义的 Future
实现的,但 C++并非通过实现 trait 而是通过鸭子类型。
与 Kt 不同的是,C++的协程只需返回一个 Future 即可,无特殊函数符号,类似于 Rust 的 async 实际上返回的是 future 一样
大概需要定义两个东西,一个是 promise,一个是 future,promise 是 future 的 factory,当返回一个 future 实现时整个函数就成为了一个协程(这也是协程的一个最准确的概念,是一个子例程),其中发生的各种行为比如说 await,yield,return 的行为都被 promise 的实现来约束
这里给出 C++20 高级编程中的约束示意 来判定一个类型是否符合 Promise 的标准
先假设它返回的是 int
template<typename P>
concept Promise = requires(P p) {
{p.get_return_object()} -> Future; //支持返回一个 Future
{p.initial_suspend()} -> Awaitable;
{p.final_suspend()} noexcept = Awaitable;
p.unhandled_exception; //处理异常的函数
requires (requires (int v) {p.return_value(v);} || requires {p.return_void();} ) //必须包含其中一个函数
}
template<typename T, typename... Ts> concept one_of = (std::same_as<T, Ts> || ...);
template <typename A>
concept Awaitable = requires(A a) {
{ a.await_ready() } -> std::convertible_to<bool>; // 判断是否需要挂起
{ a.await_suspend(std::coroutine_handle<>()) } -> one_of<void,bool, std::coroutine_handle<>>; // 挂起时执行的逻辑
{ a.await_resume() }; // 恢复时执行的逻辑
};
template <typename F>
concept Future = requires(F f) {
F::promise_type;
requires Promise<F::promise_type>;
};
从整体来说应该是这样的
struct Future{
//这个 struct promise_type 不能变
struct promise_type{
std::suspend_always initial_suspend() { return {}; };
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {};
Future get_return_object() {return Future(...)};
}
}
在构造 promise_type 之后可以通过 std::coroutine_handle::from_promise(*this) 直接获取到 std::coroutine_handle
Awaitable
这个来描述当前异步任务的状态
其中可以很明显看出来如果我们这样实现,那么其实跟 Rust Future 会差不多
struct suspend_always
{
auto res;
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<> h) const noexcept {
async_op([=](auto _res){
res = _res;
h.resume()
});
}
constexpr auto await_resume() const noexcept {
return res;
}
};
下面提到的挂起讲的是协程挂起的含义,其暗含调用 await_suspend 的含义
对于 await_ready 来说其返回的是 当前是否需要挂起 ,若没有准备好则是直接挂起,反之继续执行
对于 await_suspend 而言,这个则是代表了当前的调度逻辑,这里我使用 kotlin 的一些协程元素来描述
- 返回类型为 void,跳出当前 suspend 函数,类似于 java 的 continuation.yield 回到 continuation::run 被调用的地方,将控制流返回
- 返回类型为 bool,如果返回 true,则跳出当前 suspend 函数(同上),否则直接恢复当前 continuation,直接向下执行
- 返回类型为 coroutine_handle,指定恢复哪个 continuation,思考一个 exchange 的场景,第一个协程对应的 coroutine_handle 为 h1,其先进行 take 发现没有,则把自己挂起把 h1 找地方存一下,然后后面一个协程来 push 元素,其对应的 coroutine_handle 为 h2,此时就可以返回 h1 让其恢复
对于 await_resume 则是代表了 恢复后返回的值类似于 Future.await 的返回值
所以这个描述了一个异步任务的整体的生命周期。
Promise
这个是 Future 工厂,先每次构建 Future 再直接构建 Promise
Promise 包含了当前协程生命周期中的各种行为,各种操作符(co_await,co_return)的行为
这里给出一个 co_wait 实现的生成器代码
原理是 promise_type 来驱动一次自增 然后把结果通过 await_transform 这个函数来交给 promise
最后 future 从绑定的 promise 中获取到自增后的数据
struct Generator {
// promise 就是 awaitable 的 factory
struct promise_type {
int next_value;
// 开始执行时直接挂起等待外部调用 resume 获取下一个值
// 必须加的函数
std::suspend_always initial_suspend() { return {}; };
/**
/// [coroutine.trivial.awaitables]
struct suspend_always
{ 返回当前是否可以恢复
constexpr bool await_ready() const noexcept { return false; }
返回类型为 void,跳出当前 suspend 函数。
②返回类型为 bool,如果返回 true,则跳出当前 suspend 函数,否则直接恢复当前 continuation。
③返回类型为 coroutine_handle,指定恢复哪个 continuation。
constexpr void await_suspend(coroutine_handle<>) const noexcept {}
类似于 Future<T>.await 的返回值
以上说明中包含恢复 continuation 最后都会调用到这里来返回一个值 constexpr void
await_resume() const noexcept {}
};
*/
// 执行结束后需要挂起 最后来销毁
// 必须加的函数
std::suspend_always final_suspend() noexcept { return {}; }
// 为了简单,我们认为序列生成器当中不会抛出异常,这里不做任何处理
void unhandled_exception() {}
// Promise 对象构造完成后,通过用户提供的 Promise::get_return_object 接口来构造 Future 对象,
// 也就是协程的返回对象,该对象将在第一次挂起后返回给协程的调用者
Generator get_return_object() {
return Generator(
std::coroutine_handle<promise_type>::from_promise(*this));
}
std::suspend_always await_transform(int value) {
this->next_value = value;
return {};
}
// 没有返回值
void return_void() {}
};
std::coroutine_handle<promise_type> handle;
Generator(std::coroutine_handle<promise_type> h) { handle = h; };
Generator(Generator &&rhs) : handle(std::exchange(rhs.handle, {})) {}
~Generator() {
// 销毁协程
if (handle) {
handle.destroy();
}
}
int next() {
handle.resume();
return handle.promise().next_value;
}
};
Generator sequence() {
int i = 0;
while (true) {
co_await i++;
}
}
initial_suspend
这个函数在协程体执行之前就会被调用,可以在其中插入调度的逻辑
final_suspend
当协程执行完成或者抛出异常之后会先清理局部变量,接着调用 final_suspend 来方便开发者自行处理其他资源的销毁逻辑。
final_suspend 也可以返回一个等待体使得当前协程挂起,但之后当前协程应当通过 coroutine_handle 的 destroy 函数来直接销毁,而不是 resume。
await_transform
这个用于当前协程体内部出现 co_await 操作符的时候的转换函数
/**
首先是检查协程用户定义的 Promise 对象是否存在成员函数 await_transform,如果存在,
则令 Awaitable 对象为 await_transform(expr);
如果不存在,则令 Awaitable 对象为 expr,这一步让协程拥有控制 co_await 表达式行为的能力,
。*/
/**
首先是检查协程用户定义的 Promise 对象是否存在成员函数 await_transform,如果存在,
则令 Awaitable 对象为 await_transform(expr);如果不存在,则令 Awaitable 对象为 expr,
*/
std::suspend_always await_transform(int value) {
this->next_value = value;
return {};
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 为什么 Jvm 需要有栈协程
下一篇: Jvm 常量池
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论