chenshuo / muduo

Event-driven network library for multi-threaded Linux server in C++11
https://github.com/chenshuo/muduo
Other
14.71k stars 5.15k forks source link

muduo 后续有在现有代码上增加协程能力的计划吗 #514

Closed uuuuu-diwu closed 2 years ago

uuuuu-diwu commented 3 years ago

看着似乎没有办法 发邮件到 muduo-library@googlegroups.com

We're writing to let you know that the group you tried to contact (muduo-library) may not exist, or you may not have permission to post messages to the group. A few more details on why you weren't able to post:

就在这里提问题吧 muduo 后续有在现有代码上增加协程能力的计划吗 基于当前的 reactor 代码,在这上面增加简单的调度能力和上下文保存与 restore 能力应该就能实现协程 大致思路如下:

  1. 整体分为三种协程, 1.1 主协程-不分配执行栈, 1.2 event_loop 协程-执行event dispatch 并调用回调,需要单独分配执行栈 1.3 其他协程-执行用户设定的 function,需要单独分配执行栈
  2. 每次执行 io 操作时,将当前函数的上下文保存到一个 io 队列中,并设置一个超时回调和 io 事件 ready 回调,回调的逻辑是将协程移动到可运行队列。
  3. 每次执行 io 操作时,去可运行队列中寻找下一个协程上下文,并切换到这个上下文,如果没有找到下一个可运行的协程,则直接切换到执行 event_loop 的协程,如果有 io 事件 ready,则会回调对应的回调函数
  4. 为常用的 io 系统调用编写协程版本的函数
chenshuo commented 3 years ago

我把 muduo-library 邮件列表的回复贴在这里: https://groups.google.com/g/muduo-library/c/OIFG8uF6-Bg/m/I2uO-zcCAgAJ

长话短说:首先,我认为 Go 的并发模型非常好用,写起来比基于事件回调的风格思路要顺畅许多;但是,我目前认为在 C/C++ 里搞用纯户态的 coroutine 没有前途。

coroutine 的本质是对 OS thread 的复用,好处是比 thread 更轻量(10 倍以上),memory locality 也更好,一个程序可以有几百上千的线程 vs. 几万的 coroutine。 坏处是 OS / monitor / debugger 失去对程序的观察力,例如 /proc/pid/task 看不到 coroutine,而且 thread-local storage 和 gettid() 也都废掉了。比如我通过 OS 看到某个 thread 死锁或者 busy-loop,其实很难知道具体是哪个 coroutine 出了问题。一般的 debugger 能看到 OS threads,但看不到你自己实现的 coroutines,特别是当前没有运行的 coroutine。profiler 也不容易正确找出热点,因为看不到正确的 call stack trace。

根据我这几年所学,我认为正确的做法是让内核做上下文切换,但同时可以把调度的任务放到用户态,这样两方面的好处都占住了。

具体说来,如果 Linux 内核接受了 Google 的 switchto() 系统调用,那么 C/C++ 服务端编程就可以采用 Go 一样的编程模型,大家都轻松很多。

ref.

ps. Google 内部用 switchto() 已经很多年了。这也是我对 C++20 里的协程不感兴趣的原因。

陈硕

uuuuu-diwu commented 3 years ago

感谢作者的答复和 ref,又有新的东西可以学习了

adlternative commented 3 years ago

您好,陈硕,经过几天对 您说的这个 Google swithto() 资料的阅读,我大致可以明白它基于 futex_swap() 实现,尽管现在这个补丁似乎被 LKML 抛弃了。

这个 futex_swap() 似乎是在让当前线程 1 执行 futex_wait(futex1) 进入睡眠, 并让对期望切换的线程 2 执行唤醒操作 futex_wake(futex2) , 然后同理线程 2 也可以通过同样的方式唤醒线程 1 并让自己进入睡眠.

但我仍然有一些疑问:

  1. 在 [1] 中:"Take stack size for example: assuming the kernel stack is 10kB, if your thread itself uses 10kB of stack, you've cut the theoretical memory advantage of M:N down from the cited 1000x to a mere 2x",为何这个 switch_to() 会让理论的内存优势会下降如此严重呢?
  2. 似乎最近该补丁作者的新补丁是在 UMCG API 是做文章,意图让 Google 另一套个用户空间调度框架 Fibers 开源 ,这是否意味着 switch_to() 将只会在 Google 内部闭源使用?

[1] https://news.ycombinator.com/item?id=24084348 [2] https://lore.kernel.org/lkml/20210520183614.1227046-1-posk@google.com/

Celthi commented 3 years ago

问一句,协程(如co_await, co_yield)方式的支持,只是让用户写代码更方便一点,还是用的muduo的架构,所以陈硕说的问题和对协程的支持不冲突吧? 相当于用C++20的协程支持能力,包装一下muduo,然后用户可以使用co_awaitco_yield来使用muduo框架。

adlternative commented 3 years ago

问一句,协程(如co_await, co_yield)方式的支持,只是让用户写代码更方便一点,还是用的muduo的架构,所以陈硕说的问题和对协程的支持不冲突吧? 相当于用C++20的协程支持能力,包装一下muduo,然后用户可以使用co_awaitco_yield来使用muduo框架。

是的我觉得 co_awaitco_yield 可以使用,但目前 c++20 的协程原语写起来并不方便,同时程序的性能很可能没有提升。

chenshuo commented 3 years ago

认为协程可以简单地与高性能划等号。

根据 SIGCOMM ’21 上的文章 Understanding Host Network Stack Overheads 的测试结果,在服务器硬件上,现在 Linux 内核网络协议栈(内核 5.4+)可以用 1 个 TCP 连接(单core)打满 40Gbps 网卡的单向带宽,但需要多个 cores 和多个连接才能用完 100Gbps 网卡带宽。也就说单线程 1 个 TCP 连接就可以发送或接收 5GBytes/s。

在程序的网络吞吐量接近这个量级之前,谈性能优化恐怕为时过早。

作为对比,i7-8665U CPU 算 SHA1 的速度大约是 1.2GBytes/s/线程,只有 TCP 单连接吞吐量的 1/4 左右。就是说,如果设计不当,checksum 这一步可能会成为数据传输的瓶颈。