“流”和“异步”之间到底有什么关系?
一、你对异步编程的理解是什么?
说到“异步编程”或者“高并发编程”,你首先想到的是什么呢?
根据以往当面试官的经验:
- 青铜级的求职者,一般会说多线程、synchronized、锁等知识,更有甚者还会扯到 Redis 神马的。很显然,这类求职者对异步和高并发编程,其实是没有什么概念的;
- 白银级的求职者,则会说线程池、executor、ConcurrentHashMap 等,这类同学对异步和高并发编程,已经有了初步认识,但却还不够深入;
- 王者级别的求职者,则会对 NIO、Netty、CompletableFuture 等技术如数家珍,甚至还会谈到 Fiber。
回到问题本身,当我们谈论“异步”和“高并发”编程时,到底是在说什么呢?“高并发”其实是我们要解决的问题,而“异步”则是为了更有效地利用 CPU 和 IO 资源,来解决“高并发”问题时的编程方式。在“高并发”场景下,我们通常会使用“异步”的编程方式,来提升 CPU 和 IO 的使用效率,从而提高程序性能。
所以进一步地,我们的问题落在了选择“异步”编程方案上。
二、异步编程
目前主流的异步编程可以分为四类模式:Promise、Actor、ReactiveX 和纤程(或者说协程)。
Promise 模式
Promise 模式是非常基本的异步编程模式,在 JavaScript、Java、Python、C++、C# 等语言中,都有 Promise 模式的实现。Promise 正如其名,代表了一个异步操作在其完成时会返回并继续执行的承诺。
主要解决的是:“回掉陷阱”的问题。
回掉方式的写法是一层一层的嵌套,如果有多个回掉逻辑,就会导致代码极其复杂和混乱,不易编程和维护。
因此,出现了 Promise 方式。将嵌套形式的编程变成了「链式编程」。
比如在上一篇中的异步执行代码,正是一个 Promise 链。
CompletableFuture
.supplyAsync(() -> this.decode(ctx, req), this.decoderExecutor)
.thenApplyAsync(e -> this.doExtractCleanTransform(ctx, req, e), this.ectExecutor)
.thenApplyAsync(e -> this.send(ctx, req, e), this.senderExecutor);
从上面的图 1 可以看出,这个过程有生产者,有队列,有消费者,是不是已经非常像“流”?
Actor 模式
在这种模式中,用 Actor 来表示一个个的活动实体,这些活动实体之间以消息的方式,进行通信和交互。
Actor 模式非常适用的一种场景是游戏开发。比如 DotA 游戏里的小兵,就可以用一个个 Actor表示。如果要小兵去攻击防御塔,就给这个小兵 Actor 发一条消息,让它移动到塔下,再发一条消息,让它攻击塔。
ReactiveX 模式
ReactiveX 模式又称之为响应式扩展,它是一种观察者模式。在Java 中,ReactiveX 模式的实现是 RxJava。ReactiveX 模式的核心是观察者(Observer)和被观察者(Observable)。被观察者(Observable)产生一系列的事件,比如网络请求、数据库操作、文件读取等,然后观察者会观察到这些事件,之后就触发一系列后续动作。
纤程/协程模式
最后是纤程(fiber)模式,也称之为协程(coroutine)模式。应该说纤程是最理想的异步编程方案了,没有之一!它是用“同步方式写异步代码”的最高级别形态。
下面的图 3 是纤程的工作原理,纤程是一种用户态的线程,其调度逻辑在用户态实现,从而避免过多地进出内核态,进行调度和上下文切换。
【对异步编程的还不是很理解,需要后面在多学习】
三、异步和流之间的关系
这四种异步编程模式,它们都已经暗含了“流”的影子。
首先是 Promise 模式,当 CompletableFuture 使用的执行器,是带队列的线程池时,Promise 异步调用链的过程,在底层就是事件在队列中“流”转的过程。
然后是 Actor 模式,每个 Actor 的邮箱就是一个非阻塞的队列,Actor 之间的通信过程,就是消息在这些非阻塞队列之间“流”转的过程。
接下来就是 ReactiveX 模式,将自己定义为异步编程库的 ReactiveX,明确地将事件按照“无限流”的方式来处理,还实现了 Reactive-Streams 标准,支持反向压力功能。
最后是纤程和协程,Golang 语言明确将“队列”作为了异步和并发编程时最主要的通信模式,甚至将“通过通信来共享内存,而不是通过共享内存来进行通信”,作为一种编程哲学思想来进行推崇。
所以,在四种异步编程模式中,我们都能够发现“队列”的身影,而“队列”正是“流”计算系统最重要的组成结构。我们可以利用这种结构,来实现“流”计算的过程。
有“队列”的系统,注定了它会是一个异步的执行过程,这也意味着“流”这种计算模式注定了是“异步” 的。异步系统中存在的 OOM 问题,在“流”计算系统中也存在,而且同样是使用“反向压力”的方式来解决。
四、小结
总的来说,“流”和“异步”是相通的。其中,“异步”是“流”的本质,而“流”是“异步”的一种表现形式!