基于协程io_uring 异步网络库系列 II: 协程基本组件概要 | C++20 coroutine 教程 | io_uring 异步IO 网络框架 系列笔记

本系列通过结合 linux 的 io_uring 和 cppcoro (源码需要进行部分修改以适配 linux 下的 g++-11)在网络中的使用学习 C++20 coroutine。值得注意的是,cppcoro 目前已经暂停维护,仍然为 TS 版本的支持,同时其真异步底层支持只支持了 win32 的 IOCP(本身 cppcoro 兼容 MSVC),但是本系列不想涉及 IOCP 和 windows 的部分因为除了跨平台外,没有太大意义(如果采用 windows 的话,C# 是足够好的语言,但是目前广泛的服务器应用一般采用 linux)。

本系列本身是内容自洽的如果前提得到了满足即读者(我)必须具备了 C++20 coroutine 的状态机(当然,通用的状态机本身也可以用协程实现,不过这已经离题了,可以另外开笔记来讲怎么做)基础流程在脑子里以及 io_uring 的 liburing API(不是必须的),而这些内容都可以在上面附的文章中得到答案(以及较官方的资料,如 io_uring 的除了本身的 pdf 、邮件列表、还有专门的网站)。尽管这里废话会很多,虽然废话的存在本身可能是因为要用来构造一种常识/直觉,而省去了会简洁很多但是要求读者分布式去中心化地具有这些常识,实际学到的东西很少,没有任何的挑战,而且必须是烂尾预定的系列,但是我还没学会高效的记录方式,很容易忘掉细节。本身看这些也没有用,可能搞懂现有的基础设施的源码对调优还有一点意义。我备注一下实际如果要读源码的话,需要准备 boost asio 支持 coroutine ts 的版本(主要看 example)、cppcoro 的代码库(以及 readme 的示例)。

专栏内的所有笔记本身是和他们自洽的(也许漏了一篇讲如何理解协程和函数式编程中的 call/cc 的笔记,博客中也上传了,当然实际这系列笔记不是一个能够快速上手的,而是一个系列的学习,主要目的是供我自己复习或者有对 C++ 协程与 Proactor 网络框架编写感兴趣的读者。

目录:

cppcoro 源码级使用教程系列: 概述 | C++20 coroutine 教程 | io_uring 异步IO 网络框架 系列笔记_我说我谁呢 --CSDN博客


基本需要的基础模块我们已经明白了,我们直接分析 cppcoro 的,从而可以采用(因为cppcoro本身还是对 ts 的支持目前停止维护,可能需要修改部分代码以支持 gcc-11,实际搞这个不如等 C++26、29 asio进入标准 std::net, 以及 executor 作为 coroutine 的支持封装,cppcoro 作者目前不维护了。下一个是 facebook 的 libunifex 项目了。反正 23已经无了,建议 Web 还是回归 Jvav 或者上脚本语言吧 )到 proactor 项目中去。

本文请对照 cppcoro 的 README 阅读。GitHub - lewissbaker/cppcoro: A library of C++ coroutine abstractions for the coroutines TShttps://github.com/lewissbaker/cppcoro/

  • task: 首先是 task 类型,这个我们已经很熟悉了,就是一个延迟服务的 continuation 封装。task 是不可以复制的,可以编写 shared_task 实现线程安全的版本,但是我认为不是必须的,因为他的需求不是很必须。
  • sync_wait: C# 中 async void 的这种 top-level async operation (本身不能被 await) 通过函数 sync_wait 提供支持。注意,执行 sync_wait 函数的这个函数本身是同步执行的了,比如 main 函数。而阻塞的效果则是另外的。C# 中是没有阻塞效果的。这里cppcoro::sync_wait 的阻塞效果是通过 polling sleep + cas 或者 condition_variable (你当然要让 awaitable 最后 notify 这个 condition_variable)的 sleep 实现的。
  • async void (async scope) :真正的 top-level async void 是通过 spawn (asio 的 co_spawn)的形式提供(像 cppcoro 实际是封装了一个 onewaytask 来实现上面我们说的 asyncvoid,而且对于这种任务,我们不能让 main 函数直接结束,必须完成 join, 所以封装了一个 scope)。
  • fmap: 由于协程本身也是函数,有输入(参数)和输出(返回值),所以支持 std::apply 是一个比较重要的功能。要点很简单就是借助 when_all_ready  来实现 std::apply 的对 tuple 支持那样的效果,实际就是通过这个 when_all_ready 的 task 把 continuation 抓走,resume 的时候透过 awaiter 的 await_resume 接口通过 std::invoke/apply 执行函数,然后返回结果。当然他本身不会 co_await 那个 when_all_ready 的 awaitable,而是通过手动执行 await_suspend 运行的,这样的好处是可以套娃,比如直接把 fmap_awaiter 捕获的 continuation 让作为参数的 awaiter 去恢复,最终 complete 之后控制流将会回到 fmap_awaiter 的 await_resume 中等待获取结果,而不是在作为参数的 awaiter 中。
  • 批处理:benchmark 表示 C++ 可以同时运行上亿个协程,为了做这么多,需要协程的批处理,通过 when_all_ready 和 when_all 两个接口来支持,他们本质是一个 mux。when_all 调用了 when_all_ready (与 fmap). when_all_ready 可以让一些失败一些成功也能获取到结果,when_all 不行。实现思路不过是让 when_all_ready 直接顺序 co_await 结果,然后 co_yield 出来就行了。
  • 剩下的还有一些支持性质的,比如协程最普通的那些应用像线程池、转移协程到另一个线程运行的那些功能。还有 generator 等。
  • 本身需要提供一些 type traits。写模板函数本身也会用到大量的 type_trait ,这里要复习一下,enable_if 这种是用来优先匹配模板的(如果条件不满足会不启用他在匹配候选中)、通过 static_assert +tt 来实现类型检查、noexcept 根据匹配类型的 nothrow_ctor 来决定(ctor 失败只能通过异常实现报错,或者你可以 abort,但是析构函数是不能抛出异常的。注意异常安全的 swap 是通过类自己实现 swap,只交换内部的指针和值,不要用到 class 本身的 tmp)、其他的另说了。
  • 其他编程实践还有 对于 header-only 的头文件依赖设计问题(类之间减少耦合)、通过 detail namespace 隐藏内部类、方法等、OOP 通过基类提取公共部分、异常安全如果不通过返回值指示错误就应该用上异常(构造函数...)。
     

此处应该补充一些实际代码的对C++协程流程的复习。。。。

。。。。留空

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值