Lab4c 构建基础协程同步组件wait_group

tinyCoroLab4c 实验简介

本节我们将正式开始 tinyCoroLab4c,即构建基础协程同步组件 wait_group。如果实验者对 latch 进行了正确的实现,那么 wait_group 的构建将会十分轻松。

预备知识

⚠️预备知识即在实验开始前你应该已经掌握的知识,且在知识铺垫章节中均有涉及

  • C++ 协程 awaiter 的概念

  • C++ 模板编程

📖lab4c 任务书

实验前置讲解

本节实验涉及到的核心文件为include/coro/comp/wait_group.hppsrc/comp/wait_group.cpp,实验者需要预先打开文件浏览大致代码结构,下面针对该文件内容进行讲解。

与 lab4a 情况类似,include/coro/comp/wait_group.hpp中给出了一个非常简单的 wait_group 的定义,注意该定义仅仅是一个形式,不具备 wait_group 的正确功能,但是其类以及函数声明形式是正确的

然后…………没了🤡,因为本节实验的大部分内容你在 lab4b 中已经完成了。

⚠️注意事项

  • 请确保已阅读过tinyCoroLab Introduce章节。

  • 为了确保正确实现目标函数,实验者可能需要做一些额外操作:新增类、修改现有类的实现、补充现有类的方法和成员变量等操作,请遵循free-design 实验原则

  • 你需要仔细评估待实现的接口是否需要是线程安全的。

  • 任何导致测试卡住、崩溃等无法使测试顺利通过的情况都表明你的代码存在问题。

实验任务书

🧑‍💻Task #1 - 实现 wait_group

任务目标

实验者可能会疑惑,前几节实验涉及的协程同步组件均可以在 C++ 中找到原型,但 wait_group 好像真的没有听说过?因为这是来自 golang 中的同步组件,但本身用法与 std::latch 大致相同,首先看一下实验者需要为 wait_group 实现的函数声明:

auto add(int count) noexcept -> void; // 普通调用
auto done() noexcept -> void; // 普通调用
auto wait() noexcept -> awaiter; // 协程调用,awaiter 的 await_resume 返回 void

wait_group 与 latch 相似本身同样持有计数并在构造函数里指明初始计数,done与 latch 的count_down功能完全一致,wait与 latch 的wait功能也完全一致,但不同的是 wait_group 多了一个add,可以为 wait_group 增加引用计数,这就表明即使 wait_group 的计数在降至 0 但通过add增加,那么后续的wait_func在执行co_await wait_group.wait()时依然会陷入 suspend 状态。

有实验者可能会问在功能上 latch 似乎是 wait_group 的子集,只要 wait_group 不就可以了?原因主要是设计 latch 是想与 C++ 中的 std::latch 保持一致,但 latch 初始计数只能在构造函数指定且不能增加,而 wait_group 则适用于可能并不清楚计数具体该多少的场景,比如利用 wait_group 等待子任务完成,此时子任务的数量可能无法获取,但每次接收执行一个子任务均可以对计数加 1,这样就能正确等待子任务完成了。

另外,考虑到 wait_group 的计数可以被增加,那么实验者可能会问:会不会出现初始计数为ndone执行了n+1次导致计数为 -1,此时通过add又将计数置为 0,那么这个过程中唤醒 suspend awaiter 的逻辑是什么?在 golang 的规定中done的调用次数超过计数属于用户使用错误,会导致预期外的行为,实验者同样可以这样设计,但在 tinyCoroLab 的测试设计原则中有一条是理智,即 tinyCoroLab 的测试是基于理智用户的行为,所以测试不会出现让计数降至小于 0 的情况,实验者也可忽略这种情况的处理办法。

下面给出 wait_group 的使用场景便于实验者理解:

wait_group wg;
wg.add(1);
task<> done_func() {
  // codes...
  wg.done();
}
task<> wait_func() {
  co_await wg.wait();
  // codes...
}

涉及文件

待实现函数

  • coro::wait_group::add

  • coro::wait_group::done

  • coro::wait_group::wait

补充说明

  • 你的实现必须包含任务目标中描述的函数且函数声明形式必须一致,不然无法正常编译

  • 协程函数的返回类型可以被修改,但必须是 awaiter 或者 awaitable 类型且 await_resume 返回类型与任务书规定一致

  • 请务必考虑多线程安全性问题

  • 因为 tinyCoro 设计问题,所以协程组件恢复 suspend awaiter 时最好将其派发到其原本运行的 context

🔖测试

功能测试

功能测试场景主要针对:

  • 单个 context 下 wait_group 的 add、done 与 wait

  • 多个 context 下 wait_group 的 add、done 与 wait

完成本节实验后,实验者请在构建目录下执行下列指令来构建以及运行测试程序:

make build-lab4c # 构建
make test-lab4c # 运行

内存安全测试

在构建目录下运行下列指令来执行内存安全测试:

make memtest-lab4c

测试通过会提示 pass,不通过会给出 valgrind 的输出文件位置,请实验者根据该文件排查内存故障。

性能测试

💡tinyCoroLab预置了用于性能调优的火焰图生成脚本哦!详情请查看scripts/README.MD

tinyCoroLab Introduce章节中提到性能测试的三种模型:

  • thread_pool_stl_XX: 使用简单的线程池和 stl 组件。

  • coro_stl_XX: 使用 coro 调度器和 stl 组件。

  • coro_XX: 使用 coro 调度器和 coro 组件。

对于 lab4c 的性能测试,coro 组件即实验者实现的 wait_group,stl 组件虽然没有 wait_group,但仍然可以使用 C++ std::latch 代替,测试场景为多个函数 done 和多个函数 wait,并测量执行总耗时。

实验者只要重点关注coro_stl_XXcoro_XX模型输出的结果差异即可,该结果反映的实验者的实现与 stl 实现的性能差异。由于线程在受线程同步组件影响而陷入阻塞态时并不会选择执行其他任务,但 tinyCoro 执行引擎会,因此为了保证公平性,每个线程只会被派发一个任务,性能测试也仅仅是想重点关注实验者的实现与 stl 实现的性能差异。

在构建目录下运行下列指令来构建和运行性能测试:

make benchbuild-lab4c # 构建
make benchtest-lab4c # 运行

Last updated