Lab5a(选做) 构建进阶协程同步组件when_all
tinyCoroLab5a 实验简介
本节我们将正式开始 tinyCoroLab5a,注意!不再是构建基础协程同步组件,而是进阶协程同步组件!!!既然是进阶,那么实现上可能会有一丢丢小复杂,而本节将要实现的是一个协程函数 when_all,同时该函数也是用于构建 tinyCoro 并行计算模块的核心组成部分。
预备知识
⚠️预备知识即在实验开始前你应该已经掌握的知识,且在知识铺垫章节中均有涉及
⚠️本节实验要求实验者对C++模板的使用有一定熟练度
⚠️本节实验与后续实验无关联,因此实验者若认为本节难度超出个人能力范围可选择跳过
C++ 协程 awaiter 的概念
C++ 模板编程
C++ concepts
📖lab5a 任务书
实验前置讲解
本节实验涉及到的核心文件为include/coro/comp/when_all.hpp,实验者需要预先打开文件浏览大致代码结构,下面针对该文件内容进行讲解。
when_all 函数的主要作用是等待参数中包含的协程任务全部执行完毕后再返回,在具体实现中 建议读者将各个协程任务派发给 scheduler 从而利用多核计算的能力。借助 when_all 我们可以构建出并行计算模块,将复杂的任务拆解开后交给 when_all,从而提升任务执行效率,tinyCoroLab 目前已经实现了一些并行计算的函数,实验者可在完成本节实验后查看include/coro/parallel/parallel.hpp。
该文件对于 when_all 函数的定义给出了两种形式,即根据入参重载(使用 concepts 对函数参数做约束),这是基于实际的使用场景考虑的,下文会有更细致的介绍。实验者需要注意的是 该文件内定义的 awaiter 只是为了让项目编译通过,不具有实际意义,实验者需要实现自己需要的 awaiter,并且函数的模板参数实验者可根据需要更改,只需要保证最终实现可以通过测试即可。
💡代码是可以复用的,对于每一节实验读者都可以思考是否能复用已经完成的模块从而减少工作量
⚠️注意事项
请确保已阅读过tinyCoroLab Introduce章节。
为了确保正确实现目标函数,实验者可能需要做一些额外操作:新增类、修改现有类的实现、补充现有类的方法和成员变量等操作,请遵循free-design 实验原则。
你需要仔细评估待实现的接口是否需要是线程安全的。
任何导致测试卡住、崩溃等无法使测试顺利通过的情况都表明你的代码存在问题。
实验任务书
🧑💻Task #1 - 实现入参即任务版 when_all
该小节任务针对第一种 when_all 的定义形式,即将协程任务直接作为入参并支持变参模板,函数声明如下:
template<concepts::awaitable... awaitables_type>
static auto when_all(awaitables_type... awaitables) noexcept -> awaiter;
当参数列表的 awaitable 类型的 await_resume 全部返回 void 时,那么when_all 的作用是运行所有 awaitable 并等待所有 awaitable 返回,其返回的 awaiter 的 await_resume 函数返回类型为 void。
当参数列表的 awaitable 类型的 await_resume 全部非 void 且类型一致时,那么when_all 的作用同样是运行所有 awaitable 并等待所有 awaitable 返回,但其返回的 awaiter 的 await_resume 函数返回类型不再是 void,而是一个支持 C++ Range-based for loop 语法的容器,即 when_all 会收集参数 awaitable 的返回值并以容器形式返回,但存储顺序不做要求。
⚠️注意针对第二种情况,测试会以
for loop
的形式对when_all的结果进行遍历,因此实验者实现的 when_all 其返回的容器类型只需要支持 C++ Range-based for loop 语法即可。
实验者肯定会问如果参数列表的 awaitable 类型的 await_resume 返回类型不一致怎么办? tinyCoro 的实现是利用 concepts 对参数约束,如果出现这种情况会在编译期报错,即提醒用户这样的做法不对,当然实验者不必考虑这种情况了,至少测试中 when_all 参数列表的所有 awaitable 其 await_resume 返回类型都是一致的。
怎么理解 when_all 需要运行所有 awaitable 参数呢?是在 when_all 里一个个执行吗? 这里的运行指的是将其派发到调度器中,然后调用 when_all 的协程陷入 suspend 状态。
💡tinyCoroLab 里有
submit_to_scheduler
和submit_to_context
两种任务派发方式,实验者选取哪种都行,但只有submit_to_scheduler
才能利用多线程
实验者应当能发现 when_all 是 latch 的一种使用场景,因此实验者可借助 latch 简化 when_all 的实现。
下面给出 when_all 的使用场景便于实验者理解:
// when_all 参数列表 awaitable 全部返回 void
task<> empty_func() {
// codes...
co_return;
}
task<> when_all_func() {
co_await when_all(empty_func(),empty_func(),empty_func());
// codes...
}
// when_all 参数列表 awaitable 全部返回 int
task<int> return_int_func(int number) {
// codes...
co_return number;
}
task<> when_all_func() {
auto container = co_await when_all(return_int_func(),return_int_func(),return_int_func());
for(auto&it:container) {
// print...
}
}
涉及文件
待实现函数
coro::when_all
补充说明
你的实现必须包含任务目标中描述的函数且函数声明形式必须一致,不然无法正常编译
协程函数的返回类型可以被修改,但必须是 awaiter 或者 awaitable 类型且 await_resume 返回类型与任务书规定一致
请务必考虑多线程安全性问题
🧑💻Task #2 - 实现任务容器版 when_all
该小节任务针对第而种 when_all 的定义形式,即将封装了协程任务的容器作为入参,函数声明如下:
template<std::ranges::range range_type>
static auto when_all(range_type&& awaitables) -> awaiter;
注意定义中的入参类型为模板参数,只要满足std::ranges::range
约束即可,此时用户使用std::vctor
或者std::array
等作为 when_all 入参均可。
对于用户来讲两种 when_all 形式都有用途且以任务容器作为入参的场景更为常见,第一种 when_all 只适用于任务个数已知的情况,而实际开发中任务个数通常无法确定,所以将任务储存到容器再交给 when_all 是一种更合适的做法。
与上一任务小节类似,对于容器内的多个协程任务,其返回值类型都是相同的并且也区分 void 和 no-void 的情况,具体处理逻辑与上一任务小节一致,这里不再赘述。
下面给出第二种定义形式的 when_all 的使用场景便于实验者理解:
const int task_num = 5;
// when_all 参数列表 awaitable 全部返回 void
task<> empty_func() {
// codes...
co_return;
}
task<> when_all_func() {
std::vector<task<>> vec;
for(int i=0;i<task_num;i++) {
vec.push_back(empty_func());
}
co_await when_all(vec);
// codes...
}
// when_all 参数列表 awaitable 全部返回 int
task<int> return_int_func(int number) {
// codes...
co_return number;
}
task<> when_all_func() {
std::vector<task<int>> vec;
for(int i=0;i<task_num;i++) {
vec.push_back(return_int_func(i));
}
auto result_container = co_await when_all(vec);
for(auto&it:result_container) {
// print...
}
}
🔖测试
功能测试
功能测试场景主要针对:
when_all 参数列表 awaitable 全部返回 void 下的功能测试
when_all 参数列表 awaitable 全部返回非 void 下的功能测试
when_all 参数中的任务容器内的 awaitable 全部返回 void 下的功能测试
when_all 参数中的任务容器内的 awaitable 全部返回非 void 下的功能测试
完成本节实验后,实验者请在构建目录下执行下列指令来构建以及运行测试程序:
make build-lab5a # 构建
make test-lab5a # 运行
内存安全测试
在构建目录下运行下列指令来执行内存安全测试:
make memtest-lab5a
测试通过会提示 pass,不通过会给出 valgrind 的输出文件位置,请实验者根据该文件排查内存故障。
Last updated