从运行角度解读c++20 协程
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
测试代码
#include <coroutine>
#include <iostream>
/**
* @brief 协程特定返回类型
*/
struct task {
struct promise_type {
std::suspend_never initial_suspend() noexcept {
std::cout << " 4, initial_suspend " << std::endl << std::endl;
return {};
}
std::suspend_never final_suspend() noexcept {
std::cout << "20, final_suspend " << std::endl;
return {};
}
void unhandled_exception() noexcept {}
task get_return_object() noexcept {
std::cout << " 2, get_return_object " << std::endl;
auto tk = task{std::coroutine_handle<promise_type>::from_promise(*this)};
return tk;
}
void return_value(int v) noexcept {
std::cout << "19, return_value: " << v << std::endl << std::endl;
}
std::suspend_always yield_value(std::string&& from) noexcept {
std::cout << "14, yield_value: " << from << std::endl;
value_ = std::move(from);
return {};
}
std::string value_;
};
std::coroutine_handle<promise_type> coro_;
task(std::coroutine_handle<promise_type> h) : coro_(h) {
std::cout << " 3, task construct " << std::endl;
}
~task() { std::cout << "22, task deconstruct " << std::endl; }
std::string value() { return coro_.promise().value_; }
};
/**
* @brief 协程 co_await关键字需要的awaitable
*/
struct awaiter {
awaiter() { std::cout << " 6, awaiter construct" << std::endl; }
~awaiter() { std::cout << "11, awaiter deconstruct" << std::endl; }
bool await_ready() noexcept {
std::cout << " 7, await_ready " << std::endl;
return false;
}
void await_resume() noexcept {
std::cout << "10, await_resume " << std::endl;
}
void await_suspend(std::coroutine_handle<>) noexcept {
std::cout << " 8, await_suspend " << std::endl;
}
};
/**
* @brief 协程函数
* @return 协程特定返回类型
*/
task do_by_coroutine() {
std::cout << " 5, before co_await " << std::endl;
co_await awaiter{};
std::cout << "12, after co_await " << std::endl << std::endl;
std::cout << "13, before co_yield " << std::endl;
co_yield "123";
std::cout << "17, after co_yield " << std::endl << std::endl;
std::cout << "18, before co_return " << std::endl;
co_return 3;
}
int main() {
std::cout << " 1, main start" << std::endl;
auto result = do_by_coroutine();
std::cout << " 9, resume co_await-suspend " << std::endl;
result.coro_.resume();
std::cout << "15, get yield_value: " << result.value() << std::endl;
std::cout << "16, resume co_yield-suspend " << std::endl;
result.coro_.resume();
std::cout << "21, main end" << std::endl;
return 0;
}
运行输出
1, main start
2, get_return_object
3, task construct
4, initial_suspend
5, before co_await
6, awaiter construct
7, await_ready
8, await_suspend
9, resume co_await-suspend
10, await_resume
11, awaiter deconstruct
12, after co_await
13, before co_yield
14, yield_value: 123
15, get yield_value: 123
16, resume co_yield-suspend
17, after co_yield
18, before co_return
19, return_value: 3
20, final_suspend
21, main end
22, task deconstruct
运行输出解读
第一块 1~4
调用协程函数do_by_coroutine协程进入准备阶段。
先调用get_return_object构造协程特定返回对象在函数get_return_object中构造并返回协程特定返回对象task。
紧接着调用initial_suspend初始化协程状态文中为不挂起。
到此协程准备结束。
第二块 5~12
本块 属于最讲究的co_await操作符使用。
开始协程do_by_coroutine执行先构造出awaiter对象接着判断await_ready状态文中是false属于未ready状态所以紧接着调用await_suspend协程被挂起。
因为协程被挂起所以从协程函数do_by_coroutine中跳出往下执行以下语句
std::cout << " 9, resume co_await-suspend " << std::endl;
result.coro_.resume();
然后协程被恢复所以又跳回协程函数do_by_coroutine中对应的await_resume函数被执行。
到此co_await awaiter{} 这一行 执行结束awaiter对象被析构。打印出 “12, after co_await”。
第三块 13~17
本块 属于较为讲究的co_yield操作符使用。
目前还在协程函数do_by_coroutine中所以接着执行co_yield “123” 对应的yield_value函数被执行同时yield_value形参from被设置为“123”。上篇文中提到co_yield是co_await的变体且文中yield_value返回的是suspend_always所以协程又一次被挂起。
因为协程被挂起所以从协程函数do_by_coroutine中跳出从第一句 result.coro_.resume() 处接着往下执行
std::cout << "15, get yield_value: " << result.value() << std::endl;
std::cout << "16, resume co_yield-suspend " << std::endl;
result.coro_.resume();
然后协程被恢复所以又跳回协程函数do_by_coroutine中到此co_yield “123” 这一行 执行结束打印出 “17, after co_yield”。
第四块 18~19
本块 属于最简单的co_return操作符使用。
目前还在协程函数do_by_coroutine中所以co_return 3 被执行对应的return_value函数被执行。协程函数执行结束。
第五块 20~22
因为第四块中的co_return操作符所以协程执行结束。本块属于协程结束收尾阶段final_suspend函数首先被调用。
main函数结束。
特定返回类型task的result对象被析构。
程序退出。
协程跳转
协程最让人难以理解的就是“跳来跳去”。其实理解起来也不难。
遇到co_return就表示协程结束不会再有“跳来跳去”。
遇到co_yield或者co_await需要看awaitable是 suspend_always还是suspend_never。不过协程通常会被挂起。
只要被挂起就需要调用resume才能回到协程函数的“切出点”调用co_yield或者co_await的那一行。
只要被挂起就会从当前协程函数中跳出继续从"切入点"的下一行开始执行。