面试实战
为了帮实验者更好的使用 tinyCoroLab 准备实习和秋招特此增加了面试实战环节,我在秋招中参与了 30+ 场大厂面试且通过率超过 95%,绝大部分面试官都会将大部分时间花在对 tinyCoro 的项目考察,主要原因如下:
tinyCoro 使用的技术相比市面上其他流行的项目更加新颖
tinyCoro 涉及到的知识点众多,任何一个技术点都具有十足的技术深度
因此读者应当明白一个高质量的项目对于秋招面试是有多么重要,而对于 tinyCoroLab 作者用亲身经历帮大家验证了该项目质量的可靠性,读者可以放心大胆的将精力投入到 tinyCoroLab 中。
接下来我将整理自身关于 tinyCoroLab 的面试经历来帮助读者使用 tinyCoroLab 更好的备战秋招!
💡建议读者在完成实验后再深入理解该部分内容
问题一:你为何要做这个项目
🧑💼面试官:你先介绍一下你的 tinyCoro 项目吧
tinyCoro 是一个 Linux 系统环境下的以C++20 协程技术和 Linux io_uring 技术相结合的高性能异步协程库。高效且全能的 io_uring 和 C++20 无栈协程的轻量级切换相组合使得 tinyCoro 可以轻松应对 I/O 密集型负载,而 C++20 协程的特性使得用户可以以同步的方式编写异步执行的代码,大大降低了后期维护的工作量,且代码逻辑非常简单且清晰,除此外tinyCoro 还提供了协程安全组件,以协程 suspend 代替线程阻塞便于用户构建协程安全且高效的代码。
🧑💼面试官:所以是异步编程库对吗?现有的异步库不是很多吗?
现有的异步库存在诸多问题,比如这些库底层使用的 IO 模型例如多路复用 IO epoll 只支持网络 IO,aio 使用受限大,且容易出现伪异步的情况,而且大多数异步库使用回调的写法,这会使逻辑支离破碎,加大项目维护难度。
而 tinyCoro 就是为了解决这些问题而生,其核心使用了强大的异步 IO 技术 liburing 和 C++20 协程,得益于 liburing,tinyCoro 可以支持 Linux 上绝大多数的 IO 并且可以高效完成 IO 事件,再搭配上 C++20 协程,用户可以使用 tinyCoro 以同步的方式编写异步代码,所有的异步操作都是由库底层支持的,拒绝丑陋的回调函数写法。
综上,tinyCoro 更像是一个六边形战士,简洁优雅且高效。
🧑💼面试官:既然你把 liburing 和 C++20 协程描述的这么厉害,那为何工业界没广泛应用呢?
主要原因有两个:
liburing 从 2018 年由 Linux 官方人员发起,但直到近几年才达到一个稳定可用的程度,因此受众有限。
C++20 协程存在很多争议,即是否能提高程序性能,协程单独使用是没有什么用的(请实验者想想为什么),甚至会因为创建协程资源对项目引入额外的运行开销,因此必须找到另外一门技术,让协程真正发挥作用。
针对第二个原因,我想我为 C++20 协程技术找到了搭档,在经过调研发现 liburing 简直为协程量身打造,而在开发完 tinyCoro 后对其进行压测其结果确实达到了我的预期。
另外该技术没在工业界广泛应用的一个重要原因便是其并不能带来巨大的性能提升,可能只有百分点级别,这并不值得为已有项目更换底层技术,但是我还是看好 C++ 协程技术和 liburing 未来的发展。
问题二:tinyCoro 的内部具体设计
🧑💼面试官:介绍一下 tinyCoro 的具体设计吧
tinyCoro 的核心部分多线程执行引擎由engine、context和scheduler三部分组成。
每个engine持有一个 io_uring 实例和无锁工作队列,工作队列用于存储协程任务,io_uring 实例用来处理 IO 任务。
每个context持有一个engine和一个工作线程,其中工作线程利用事件循环的方式驱动 engine 执行完所有的任务。
scheduler在全局只存在一个实例,自身持有多个 context 并负责将新产生的任务根据负载均衡逻辑派发到指定context。
综合来看三者之间的关系就像一个军队,engine为武器,context为士兵,每个士兵持有一把武器且只有持有了武器的士兵才可以作战,scheduler作为司令官负责为士兵派发任务。
对于 IO 执行部分,每当协程发起一个协程任务时会采用co_await io_awaiter的调用,io_awaiter会从 io_uring 获取 sqe 并根据 IO 类型填充 sqe 然后提交,注意为 sqe 填充的信息包含一个回调函数,随后该协程陷入 suspend 状态并转移执行权,这样工作线程可以继续处理下一个任务,等到 IO 执行完成后工作线程取出 cqe 并调用回调函数,回调函数会向任务队列提交协程句柄从而恢复协程执行。在用户看来这就是个同步调用,但实际上执行引擎会采用异步的方式执行。
🧑💼面试官:tinyCoro 的设计有哪些重难亮点呢?
主要有如下几点:
线程独立的执行引擎: 多线程执行引擎中各个 context 持有一个工作线程且彼此相互独立,通过
scheduler来保证任务的均衡分配,这样既能利用多线程又可以最大限度降低线程竞争带来的性能损耗。基于 eventfd 的轻量级事件循环: tinCoro 执行引擎本身的事件循环是采用轻量的 eventfd 完成的,在没有任何任务时线程会阻塞在 eventfd 读操作上,当 io_uring 产生 IO 完成事件以及任务队列收到新任务时会自动向 eventfd 写值,这保证了线程被及时唤醒继续处理任务,eventfd 的读写是十分轻量的,一次读写耗时不超过 1us,因此基于 eventfd 的轻量级事件循环十分高效。
scheduler 对 context 的智能化管理: 只要用户向
scheduler提交了任务,那么只需要调用scheduler::loop便可以自动等待所有任务完成,对于特定的context即使执行完所有的任务也不会立刻终止,因为只要别的context还在运行就表明scheduler可能会向该context派发新任务,只有等待全部context均执行完任务,scheduler才会统一发送停止信号。(该部分实际更复杂,读者可自行补充)高效的协程同步组件: 线程同步组件用来同步并发线程的行为,而协程同步组件则是用来同步并发协程的行为,当函数因线程同步组件而阻塞时会导致线程阻塞,但当协程函数因协程同步组件陷入阻塞时只会使协程陷入阻塞并转移执行权,此时线程继续执行下一个任务,保证 CPU 的高利用率。
另外 tinyCoro 采用了高质量开源 C++ 项目的组织方式,内含大量的功能、内存安全以及性能测试,并且有 tinyCoro 实现的tcp echo server经测试可以达到 100wQPS。
🧑💼面试官:tinyCoro 的性能表现如何呢?
我在个人笔记本上测得利用 tinyCoro 搭建的tcp echo server在 100 并发和 1kbyte 负载场景下能达到 100wQps,性能略胜基线模型rust_echo_server,而rust_echo_server本身就是专门为tcp echo server定制的性能极高的程序,所以实验结果可以证明 C++ 协程搭配 io_uring 的高效性,另外关于测试的更详细信息我都在项目中有记录。
🧑💼面试官:你觉得 tinyCoro 的使用场景会有哪些?
如果用户需要搭建一个服务需要处理各种类型的 IO 事件并对性能有较高要求且开发过程尽量简单,那么 tinyCoro 是个不错的选择!
🧑💼面试官:后续你会如何优化 tinyCoro?
tinyCoro 需要优化的点有很多,主要分为如下:
sqe 消费限制: tinyCoro 在发起 IO 前会先获取 sqe,而 io_uring 的可用 sqe 是有限制的,如果可用 sqe 消费完那么 tinyCoro 会得到空指针 sqe 并且操作该 sqe 就会引发 core dump,后续可以对此进行优化。
高效的负载均衡: scheduler 对各个 context 的任务派发逻辑是由 dispatcher 决定的,目前 tinyCoro 仅实现了 round-robin 逻辑的 dispatcher,可以再拓展 dispatcher 的模板类从而实现更高效的负载均衡逻辑。
网络层协议拓展: tinyCoroLab 默认添加了 tcp 支持,后续会尝试添加 HTTP 和 rpc 支持。
多线程执行引擎优化: 目前 tinyCoroLab 的多线程执行引擎是有多个 context 组成并由 scheduler 负责任务调度,各个 context 之间彼此独立且均持有一个工作线程和一个 io_uring 实例,这样的设计可以有效避免线程竞争带来的消耗,后续会参考 golang 的 gmp 设计来优化此部分。
🧑💼面试官:恭喜你面试通过了!
直接通过了?哈哈当然不会了,面试官是不会当场告知你结果的,但我相信在你的精心准备和 tinyCoroLab 加持下你一定会取得满意的面试结果!
Last updated