tinyCoroLab Docs
直通tinyCoroLab源码直通tinyCoro源码
latest
latest
  • 🚀开启你的tinyCoroLab之旅!
  • 📮致读者
  • 👊C++协程入门
    • 协程初探
    • 有栈协程VS无栈协程
    • C++协程入门实践
    • 从编译器视角揭秘 C++ 协程
    • 协程调用优化
  • 🌟认识io_uring
  • 📖tinyCoroLab实验介绍
  • 📘Lab1 构建协程任务封装
    • Lab1 构建协程任务封装
    • Lab1 实验解析
  • 📗Lab2 构建任务执行引擎
    • Lab2a 构建任务执行引擎engine
    • Lab2a 实验解析
    • Lab2b 构建任务执行引擎context
    • Lab2b 实验解析
  • 📙Lab3 封装异步I/O执行模块
  • 📕Lab4 构建基础协程同步组件
    • Lab4pre 如何构建协程同步组件
    • Lab4a 构建基础协程同步组件event
    • Lab4a 实验解析
    • Lab4b 构建基础协程同步组件latch
    • Lab4b 实验解析
    • Lab4c 构建基础协程同步组件wait_group
    • Lab4c 实验解析
    • Lab4d 构建基础协程同步组件mutex
    • Lab4d 实验解析
  • 📓Lab5 构建进阶协程同步组件
    • Lab5a(选做) 构建进阶协程同步组件when_all
    • Lab5a 实验解析
    • Lab5b 构建进阶协程同步组件condition_variable
    • Lab5b 实验解析
    • Lab5c 构建进阶协程同步组件channel
    • Lab5c 实验解析
  • ✨tinyCoro Bonus Lab
  • 🎯tinyCoro悬赏令
  • ⚔️面试实战
    • 面试实战
    • tinyCoro面试相关问题
  • 🚩实验总结-终点亦是起点
  • 🪐番外杂谈
    • 从编译器视角揭秘 C++ 协程
    • 协程调用优化
  • 📌更新日志
Powered by GitBook
On this page
  • tinyCoroLab2b 实验简介
  • 📖lab2b 任务书
  • 实验前置讲解
  • ⚠️注意事项
  • 实验任务书
  • 🔖测试
  1. 📗Lab2 构建任务执行引擎

Lab2b 构建任务执行引擎context

tinyCoroLab2b 实验简介

本节我们将正式开始 tinyCoroLab2b,即构建任务执行引擎的子模块 context。在 lab2a 中我们将 engine 比作武器,context 比作士兵,只有装备了武器的士兵才能执行任务,而实现 context 的过程就像是在传授士兵如何去使用 engine 这把武器。在完成本节 context 的实现后 tinyCoro 的基础部分就正式完成了,即可正式对外提供服务。

预备知识

⚠️预备知识即在实验开始前你应该已经掌握的知识

  • C++ 智能指针

  • C++ jthread

📖lab2b 任务书

实验前置讲解

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

首先每个 context 都是拥有一个 engine 的,并且会额外开启一个工作线程来驱动 engine 执行任务,这里 context 使用 C++ 智能指针和 jthread 来管理工作线程。C++ 智能指针实验者应该熟悉,而 jthread 是 C++20 提供的新的线程类,其相比原本的 thread 增加了自动 join 和线程取消等功能,简化了线程管理的复杂性,比如 jthread 会在生命周期结束后自动 join,jthread 运行的函数可以带有 stop_token 来传递停止信号。

context::start()函数已经预先实现好,代码如下,实验者根据需要自行添加内容:

auto context::start() noexcept -> void
{
    m_job = make_unique<jthread>(
        [this](stop_token token)
        {
            this->init();
            this->run(token); // 核心逻辑
            this->deinit();
        });
}

函数run()是 context 核心运行逻辑,由实验者实现。

总体上讲 lab2b 的代码量是较少的,但我仍然会为你展示 context 与 engine 的逻辑交互图来加深你的理解。

注意!交互图仅仅为了方便你理解,context 的循环并不一定要按图中的顺序来,总之要保证在一次次循环中处理掉所有任务。

💡如果你对任务循环还不太理解,请回看 lab2a 中关于任务循环的图文讲解

最后是对 scheduler 的讲解,读者可以打开include/coro/scheduler.hpp和src/scheduler.cpp大致浏览代码。

scheduler 掌管多个 context 来充分利用多线程,用户无需管理各个 context,这是 scheduler 的职责,用户只需要把任务交付给 scheduler 即可。

scheduler 采用单例模式实现,用户使用 tinyCoro 构建程序主要是下述流程:

scheduler::init(); // 入参为 context 数量,默认为当前机器的逻辑 CPU 核心数
submit_to_scheduler(task);
// more submit...
scheduler::loop(); // 等待全部 context 完成任务后再关闭全部 context 并返回 

scheduler 维护了一个 dispatcher 用于实现submit_to_scheduler函数的任务分发逻辑,其模板参数为分发策略,其核心的dispatch函数会返回一个 id 指定该任务具体派发的 context,tinyCoro 默认实现了round-robin式的任务派发逻辑,实验者可以根据需要实现更高效的派发逻辑。

在 1.1 版本,scheduler 需要由用户来实现其最核心的逻辑scheduler::loop(),在该函数中,scheduler 会启动所有 context,并且等待所有 context 全部完成任务后才统一发送关闭信号,而在 1.1 之前的版本 context 在完成自己的任务后会立刻关闭,这样正在运行的协程就无法安全地将新任务派发给 scheduler,因为 scheduler 可能会把任务交付给一个已经关闭的 context。

⚠️注意事项

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

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

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

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

实验任务书

🧑‍💻Task #1 - 完善 context 初始化以及外部交互 API

任务目标

对于 context 的init()与deinit(),其实现的注意事项与 engine 相同,任何需要被 init 或者 deinit 的成员变量均应该被正确处理,不要忘了将 context 注册到线程局部变量了使得协程运行时可通过local_context()获取与当前线程绑定的 context。

对于外部用于提交任务的submit_task(std::coroutine_handle<> handle),只需要调用 engine 的对应方法即可。

对于 scheduler 用于通知 context 的notify_stop(),实验者可以自行查资料看看 jthread 是如何搭配 stop_token 实现发送和接收停止信号的。

对于外部调用的register_wait(int register_cnt)和unregister_wait(int register_cnt),这里需要为读者强调一下其作用。对于引用计数这个概念实验者一定不陌生,这是一种常见的确保资源正确释放的技巧,C++ 的智能指针其原理便是使用了引用计数,那么 context 和引用计数又有什么关系呢?

在之后的 lab4 和 lab5 中我们将会实现各种协程同步组件,用 mutex 来举例,如果一个协程尝试对已经被锁住的 mutex 上锁,那么该协程会陷入 suspend 状态,然后返回到 engine 的exec_one_task()函数继续后续流程。那么问题来了,前面讲过在短期运行模式下,scheduler 会直接向 context 发送停止信号,context 驱动 engine,此时 engine 发现有一个任务待执行,而执行的正是对 mutex 加锁失败而陷入 suspend 状态的协程,当执行权返回到 engine 的exec_one_task()时,此时 engine 任务队列为空且没有 IO 任务,context 察觉到了这一点,它检查了一遍停止条件:收到停止信号且 engine 中没有任何待执行的任务(包括 IO 任务),它毅然决然地选择退出循环,终止工作线程。别告诉我此时你什么都没察觉,因为…………一个协程未能得到完整执行且内存泄漏啦😱!!!

而通过添加引用计数功能,context 的停止条件也增加了一条:检查引用计数是否为 0。上述陷入 suspend 状态的协程会调用local_conetxt()获取与其绑定的 context 并在陷入 suspend 状态前调用register_wait(int register_cnt)增加引用计数,而在协程恢复后调用unregister_wait(int register_cnt)来减少引用计数,这样才能拿保证所有的任务全被执行后 context 才会退出。

涉及文件

  • include/coro/context.hpp

  • src/context.cpp

待实现函数

  • coro::context::init()

  • coro::context::deinit()

  • coro::submit_task(std::coroutine_handle<> handle)

  • coro::register_wait(int register_cnt=1)

  • coro::unregister_wait(int register_cnt = 1)

  • coro::notify_stop()

补充说明

notify_stop()如果只是实现了其表面功能的话可能会出现问题,至于为什么?这正是实验的一个小难点,留给实验者自己发现并解决吧!

🧑‍💻Task #2 - 完善 context 核心任务循环

任务目标

本次任务仅仅涉及一个函数run(stop_token token),这也是 context 最核心的工作线程函数,用于在循环中驱动 engine 执行完所有任务并优雅退出。由于 lab2a 以及前置讲解已经为本次任务铺垫了很多内容,因此不再过多赘述。

涉及文件

  • include/coro/context.hpp

  • src/context.cpp

待实现函数

  • coro::context::run(stop_token token)

🧑‍💻Task #3 - 完善 scheduler 全局执行引擎管控

任务目标

在实验前置讲解中有提到 1.1 版本之前存在运行模式这一概念,短期运行模式下调用submit_to_scheduler会向已经停止的 context 派发任务,这是不合理的,而在 1.1 版本 tinyCoro 正式移除了运行模式这一概念,修复了上述问题,scheduler 对 context 的管理能力有所改进。

我们先来看用户使用 tinyCoro 的一个范例:

scheduler::init();
submit_to_scheduler(func());
// repeat submit...
scheduler::loop(); // wait all context finish

在向 scheduler 提交完任务后仅需要调用loop方法即可,该方法会启动所有 context,然后等待全部 context 都完成任务后再统一关闭 context,这样各个 context 在运行过程中可以放心的把任务交付给 scheduler 来派发,比如当前只存在一个任务且正在被某个 context 执行,即使其他 context 没有任何任务也不会退出,而是陷入阻塞态,此时正在运行的 context 产生了新任务,那么其余 context 便可以接收该任务。

对于本节任务,我会采用接力棒的形式测试 scheduler 的逻辑,即我会在 scheduler 启动前提交仅一个协程任务,但该协程任务会在运行过程中向 scheduler 提交自身,这样各个 context 均会接收到该协程任务,实验者实现的 scheduler 必须保证所有的协程任务均得到执行。

另外需要注意 context 的停止不一定必须由 scheduler 来控制,比如单个 context 运行场景:

context ctx;
ctx.init();
ctx.submit_task(...);
// repeat submit...
ctx.start(); // 启动运行
ctx.join(); // 等待 context 完成所有任务
ctx.deinit();

在这种情况下 context 必须保证完成所有任务然后自动停止,换句话说,工作线程此时应该能自动调用notify_stop,不过具体怎么在无 scheduler 管理的情况下自动停止就交给实验者实现吧!

涉及文件

  • include/coro/context.hpp

  • src/context.cpp

  • include/coro/scheduler.hpp

  • src/scheduler.cpp

待实现函数

⚠️本节实验可能需要你在多处做出修改,哪怕是非标记区域

  • context.hpp和context.cpp所有带标记的函数

  • scheduler.hpp和scheduler.cpp所有带标记的函数

🔖测试

功能测试

功能测试场景主要针对:

  • 多线程向 context 提交任务

  • 基于单个 context 的混合任务执行

  • 基于 scheduler 的混合任务执行

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

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

内存安全测试

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

make memtest-lab2b

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

PreviousLab2a 实验解析NextLab2b 实验解析

Last updated 1 month ago

ch07_lab2b_p1