# 异步系列-序言

## 1. 想法

在工作 2 年的时候，我老大让我去研究[seastar](https://github.com/scylladb/seastar)这个框架，由于那个时候的技术水平限制我一直不明白它到底做了什么，导致性能上很牛逼。但是在那个时候我开始接触到了异步这个概念，经过了大约 5 年的时间，由于工作经验的沉淀，慢慢的对这个概念有了那么一丝的理解。给自己立了一个 flag，准备在半年之后在工作中分享这个主题

主要会分享内容有

* asio 的异步框架
* golang 的协程调度， 本质上是 golang 的异步调度 runtime
* rust 的 async/await
* c++ 最新的 stackless coroutine

以上这些内容是目前自己一定会输出的，后面会有一些更加业务层面的异步讨论，不过我现在还没有想好;

## 2. 关于异步

真的开始这个系列之前，先聊聊异步本身吧， why, what, how;

* 为什么需要异步呢？

这个观点主要是之前在逛知乎的时候看到了，大概意思是：异步本质上是为了尽可能的提高 cpu 有效利用率; 随着技术的发展 cpu 的速率其实已经远比其他的设备要快很多的，即使内存速度也是比 cpu 要慢几个数量级; 通常程序可以分为 2 种类型，分别为数据密集型 (IO 密集型) 和计算密集型的程序，计算密集型程序主要是通过 cpu 来完成计算任务，比如加密、编码等，对 cpu 有效利用都很高；但是数据密集型 (IO 密集型) 程序，主要是与外界设备进行交互，这种统称为 IO 操作；目前比较多的 IO 设备有：


* 磁盘/ssd

* 内存

* 网络

* ...

    <img src="https://cdn.nomoshen.com//vscode_img/20210716053518-2021-07-16.png" alt="计算机不同 op 的耗时" style="zoom:50%;" />

这些设备的速度和 cpu 相差好几个数量级，当一个 IO op 从 cpu 发送给设备的时候，这个时候设备的反应对 cpu 来说是非常非常慢的，如果 cpu 等待设备返回的话，那么 cpu 真正有效的工作时间都只有一点点，其他时候都是等待设备的返回。为此硬件工程师和软件工程师都发挥了逆天的能力来改造这些问题，而改造的本质都是操作设备的异步化，不要让 cpu 等待这些设备的反应，而是释放 cpu 去做其他更加重要事情，等设备反应过来再来通知 cpu. 而手段主要有几个：

* 硬件：通常的解决方式让设备能支持中断的方式来异步通知 cpu; 大部分硬件都是异步化的。当然也有一部分的原因在于一些操作比较简单但是非常消费 cpu，通过加入协作芯片来协助 cpu，比如 DMA 或者 FPGA，DMA 本质上就是觉得用一块专用芯片来进行完成内存 copy; FPGA 的话目前一些大厂会将一些固定的算法或者功能硬件化，比如 gzip，leveldb 中的 compaction 这些操作全部迁移到 fpga 上，这样让 cpu 更加专注于重要的事情。

* 软件：

    * os: 单进程到多进程到多线程等演变
    * 中断
    * 各种异步框架支持
    * ...

所以异步出现的历史目的是为了提高 CPU 的利用率， 通过异步化操作让 CPU 尽可能都是满负荷的工作，也是 CPU 作为处理器的目标所在。当然异步肯定不只是为了 CPU，只不过在计算机历史的过程中，异步的主要的点在于 CPU 的利用率。 人类社会的分工，流水线产生本身也是异步一种体现，其本质也是为了极大化效率。 下面聊一些计算机异步技术的相关发展：

1. 从单进程到多进程支持; 如果你明白了所有的异步都是对 CPU 来说的话，那么多进程的的支持其实也是一个异步进化，将一个进程想象为 OS 的一个 OP 操作的话，多进程就是多个 OP，如果一个 OP(进程) 阻塞住 OS 就会进行切换；** 针对 CPU 的行为描述应该是：进程 A 你现在有其他的事情要做，那你先去做那个事情吧，等你完成了其他事情的时候再来找我吧;(本质上 CPU 空闲出来可以去做其他的事情了) ** ，只不过这个 OP 操作太大了，既不能太多切换的代价也很大。
2. 线程的出现; 本质上也没有变化，只不过线程这个 OP 的粒度更加小，切换成本更加小；
3. 基于回调的异步处理；按照自己理解，可能大部分的回调实现只不过是基于 os 层面线程模型的情况下的用户层面的优化，当然[windows 的 iocp](https://docs.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports) 本质上是操作系统支持的异步化，而 Linux 则是实用多路复用 IO 来模拟的，本质上是同步的。这种优化的本质是：
    * Worker thread 能持续高效的进行工作，将当前可以被运行的任务都运行；这样 worker thread 的 cpu 利用率就很高；
    * 对于会导致 worker thread 被阻塞的操作则都迁移到 IO thread pool，这个线程池可能是 1/n 个线程，这些线程主要做的事情就是用来等待 io 操作的完成并且 worker thread 对应的 op's callback 可以进行了; 

<img src="https://cdn.nomoshen.com//vscode_img/image-20210716062043538-2021-07-16.png" alt="回调模型" style="zoom:50%;" />

    4. 协程 (coroutine)；和基于回调的方式实现逻辑是类似的 (上图)，区别点在于对用户的友好性极大的提升。协程的优势在于用同步的逻辑写异步的代码，估计这也是目前 golang 如何大火的原因之一，因为 golang 让写出性能非常不错的程序的门槛拉低了很多很多，只要你用 golang 写代码，基本上性能方面不会差很多，除非你的程序是计算密集型的。

  实现逻辑：在用户层构建一个比线程粒度更加小的 op，这个 op 是用户自己来定义的，占用资源更加小、切换成本更加低，并且对底层是无感的；可以极大的增加 op 的个数来并发运行，底层 worker thread 去运行**runnable** 的 op，这样底层 worker thread 就非常高效。目前有两种实现 coroutine 的方式：

      * stackful : 代表 golang
      * stackless: rust 的`async/await`,C++ 20 最新提出的 coroutine
    
     对于区别点可以 Google 一下。自己对这块还没有很深入研究，但是本质上在于 stackful 模拟了线程切换上下文切换这么一套东西，而 stackless 则是模拟一套函数调用，而对应的实现就在于 stackful 如果要做到通用，就必须有自己的 runtime，不然不同的架构下面上下文切换的方式会不一样；而 stackless 则是用一套标准的函数调用来实现的，也就是它没有依赖底层操作系统寄存器的东西，用现有的东西来实现的；哈哈，可能讲的不对；
    
     聊到协程，聊一下一开始线程出现的时候，遇到的问题；当时的网络编程的架构大部分是如下图;叫做每链接每线程的模型，这种模型好处在于简单，而且对 cpu 来说也能提高效率，当年还没类似于 epoll 或者其他的操作系统支持的 api 的时候，用线程将 cpu 的性能提高是唯一的方式，但是这个架构的问题主要在于量到一定级别之后，对 os 来说非常的沉重，这种沉重体现在资源和切换上，所以才会有了[C10K problem](https://en.wikipedia.org/wiki/C10k_problem)的问题；到了协程的时代，其实这个架构就非常流行了，但是区别点在于可能是`one connection one coroutine`，而底层却使用了现代异步模型，从而极大的提高了本身的性能和易用性。

<img src="https://cdn.nomoshen.com//vscode_img/one_connection_one_thread-2021-07-14.png" alt="one_connection_one_thread" style="zoom:33%;" />

## 3. 总结

上面大概的聊了一下关于异步的一些知识；这是我这半年慢慢在思考的东西；我在 twitter 上曾经发言说：难道异步的本质是`producer + queue + consumer`吗？ 虽然这个言论非常的底层或者 low 了点，但是这个模型其实就是人类历史上的流水线模型，所以**异步的本质是提高效率**; 为什么能提高效率呢？因为 queue 中存在的 task 对于下游 worker 来说是直接可以运行的，在分工明确的情况下，对应下游的 worker 可以一直不停的工作着，这样对于 worker 来说效率就非常高，不需要等待；目前流式计算就是类似于这种模式，而进程内部的话可能 worker 的角色不会划分的如此细。

<img src="https://cdn.nomoshen.com//vscode_img/20210716071206-2021-07-16.png" alt="流水线" style="zoom:50%;"  />

对于未来我比较看好 rust 的这种 stackless 的协程模型，其实我最近半年一直思考为什么 rust 如此自然的模型就没人早提出来呢？ 目前还没有答案； 

这是异步系列的第一篇文档，下一个文章应该是 asio 的分析，会结合原理 +mongo 本身的使用来分析这个库，当然我没有分析它的协程，可能要到后面几篇再聊，比如和 goroutine 一起聊;
