Lab5b 构建进阶协程同步组件condition_variable
tinyCoroLab5b 实验简介
本节我们将正式开始 tinyCoroLab5b,即构建进阶协程同步组件 condition_variable,本节的实验涉及到 mutex 的使用,请实验者确保 lab4d 已完成。
预备知识
⚠️预备知识即在实验开始前你应该已经掌握的知识,且在知识铺垫章节中均有涉及
C++ 协程 awaiter 的概念
📖lab5b 任务书
实验前置讲解
本节实验涉及到的核心文件为include/coro/comp/condition_variable.hpp和src/comp/condition_variable.cpp,实验者需要预先打开文件浏览大致代码结构,下面针对该文件内容进行讲解。
tinyCoro 中的 condition_variable 与 C++ 的 std::condition_variable 功能是一样的,因此我们首先需要回顾一下 std::condition_variable 的用法。
wait: 阻塞当前线程,直到另一个线程调用同一个 std:condition_variable 实例的 notify_one 或 notify_all 方法,或者直到指定的谓词函数 (即条件) 返回 true。如果没有收到通知,即使条件为 true 也不会继续执行。如果收到通知了,但是条件不成立将仍然被阻塞,不会执行。
notify_one: 唤醒所有等待该条件变量的线程,通常用于广播通知。
notify_all: 唤醒一个等待该条件变量的线程,通常用于优先级调度或队列处理。
上述只是列出了部分方法,但对于实现 lab5b 已经够了,include/coro/comp/condition_variable.hpp中给出了一个非常简单的 condition_variable 的定义,注意该定义仅仅是一个形式,不具备 condition_variable 的正确功能,但是其类以及函数声明形式是正确的。
需要额外注意的是 std::condition_variable 可以与 std::mutex 搭配,因此 coro::std::condition_variable 也会与 coro::mutex 搭配。
⚠️注意事项
请确保已阅读过tinyCoroLab Introduce章节。
为了确保正确实现目标函数,实验者可能需要做一些额外操作:新增类、修改现有类的实现、补充现有类的方法和成员变量等操作,请遵循free-design 实验原则。
你需要仔细评估待实现的接口是否需要是线程安全的。
任何导致测试卡住、崩溃等无法使测试顺利通过的情况都表明你的代码存在问题。
实验任务书
🧑💻Task #1 - 实现 condition_variable
任务目标
实验前置讲解中提到的 std::condition_variable 的部分方法便是我们需要在 lab5b 完成的方法,其具体功能不再赘述,lab5b 要求实验者为 condition_variable 实现的功能如下:
auto wait(mutex& mtx) noexcept -> awaiter; // 协程调用,awaiter 的 await_resume 返回 void
auto wait(mutex& mtx, cond_type&& cond) noexcept -> awaiter; // 协程调用,awaiter 的 await_resume 返回 void
auto wait(mutex& mtx, cond_type& cond) noexcept -> awaiter; // 协程调用,awaiter 的 await_resume 返回 void
auto notify_one() noexcept -> void; // 普通调用
auto notify_all() noexcept -> void; // 普通调用
对于wait
,第一个参数为 tinyCoro 的 mutex,并且可以选择附带一个条件谓词参数。当不带条件谓词时,协程会直接陷入 suspend 状态,如果带条件谓词,会先检查条件谓词的是否为 true,是则恢复运行,否则陷入 suspend 状态,注意这与 std::condition_variable 的行为不同,std::condition_variable 的 wait 在带谓词的情况下会直接让线程阻塞,只有被唤醒后才会检查谓词的结果,实验者按照 lab5b 的规定来就好。
对于notify_one
和notify_all
,均会唤醒因调用condition_variable.wait
陷入 suspend 状态的协程,前者会唤醒一个,后者会唤醒全部,被唤醒的协程如果存在条件谓词,会检查谓词的结果,如果为 true 则尝试获取锁,否则继续陷入 suspend 状态。另外对于notify_one
如何挑选一个协程来唤醒不作要求,只要保证正确唤醒一个就行。
💡调用 notify_one 或者 notify_all 一定要处于持有锁的状态吗? 参照 std::condition_variable 的行为,持有锁和发起 notify 没有什么关联。💡发起 notify 但没有陷入 suspend 的协程怎么办?会使得之后将要陷入 suspend 状态的协程直接恢复吗? 参照 std::condition_variable 的行为,通知行为不会累加,没有陷入 suspend 的协程那就忽略,之后将要陷入 suspend 状态的协程会被之后的 notify 唤醒。
如果你对 lab5b 的 condition_variable 的某些行为还存在疑惑,那么参照 std::condition_variable 怎么做就可以了,唯一的不同点刚才已经提过。
下面给出 condition_variable 的使用场景便于实验者理解:
// 场景一:保证全局操作有序性
condition_variable cv;
mutex mtx;
int global_id;
task<> func(int id) {
auto guard = co_await mtx.lock_guard();
co_await cv.wait(mtx, [&](){id==global_id;});
global_id+=1;
cv.notify_all();
}
// 场景二:协程安全的多生产者多消费者队列
class queue{
public:
task<> push(int number) {
auto guard = co_await mtx.lock_guard();
co_await producer_cv.wait(mtx, [&](){que.size()<capacity;});
que.push_back(number);
consumer_cv.notify_one();
}
task<int> pop() {
auto guard = co_await mtx.lock_guard();
co_await consumer_cv.wait(mtx, [&](){!que.empty();});
auto number = que.front();
que.pop();
producer_cv.notify_one();
co_return number;
}
private:
const int capacity;
condition_variable producer_cv;
condition_variable consumer_cv;
mutex mtx;
std::queue<int> que;
};
从上面的例子中可以看出在使用上 tinyCoro 的 condition_variable 与 std::condition_variable 几乎一致,实验者最好根据具体使用场景仔细评估自己的设计方案是否正确。
涉及文件
待实现函数
coro::condition_variable::wait
coro::condition_variable::notify_one
coro::condition_variable::notify_all
补充说明
你的实现必须包含任务目标中描述的函数且函数声明形式必须一致,不然无法正常编译
协程函数的返回类型可以被修改,但必须是 awaiter 或者 awaitable 类型且 await_resume 返回类型与任务书规定一致
请务必考虑多线程安全性问题
因为 tinyCoro 设计问题,所以协程组件恢复 suspend awaiter 时最好将其派发到其原本运行的 context
🔖测试
功能测试
功能测试场景主要针对:
利用 condition_variable 保证协程执行的有序性
利用 condition_variable 构建生产者和消费者模型
完成本节实验后,实验者请在构建目录下执行下列指令来构建以及运行测试程序:
make build-lab5b # 构建
make test-lab5b # 运行
内存安全测试
在构建目录下运行下列指令来执行内存安全测试:
make memtest-lab5b
测试通过会提示 pass,不通过会给出 valgrind 的输出文件位置,请实验者根据该文件排查内存故障。
性能测试
💡tinyCoroLab预置了用于性能调优的火焰图生成脚本哦!详情请查看scripts/README.MD。
在tinyCoroLab Introduce章节中提到性能测试的三种模型:
thread_pool_stl_XX: 使用简单的线程池和 stl 组件。
coro_stl_XX: 使用 coro 调度器和 stl 组件。
coro_XX: 使用 coro 调度器和 coro 组件。
对于 lab5b 的性能测试,coro 组件即实验者实现的 condition_variable 和 mutex,stl 组件即 C++ std::condition_variable 和 std::mutex,测试场景分为利用 condition_variable 保证执行的有序性和利用 condition_variable 构建生产者和消费者模型,并测量执行总耗时。
实验者只要重点关注coro_stl_XX和coro_XX模型输出的结果差异即可,该结果反映的实验者的实现与 stl 实现的性能差异。由于线程在受线程同步组件影响而陷入阻塞态时并不会选择执行其他任务,但 tinyCoro 执行引擎会,因此为了保证公平性,每个线程只会被派发一个任务,性能测试也仅仅是想重点关注实验者的实现与 stl 实现的性能差异。
在构建目录下运行下列指令来构建和运行性能测试:
make benchbuild-lab5b # 构建
make benchtest-lab5b # 运行
Last updated