CompletableFuture:为啥我们需要阶段?

Posted

技术标签:

【中文标题】CompletableFuture:为啥我们需要阶段?【英文标题】:CompletableFuture: Why we need stages at all?CompletableFuture:为什么我们需要阶段? 【发布时间】:2022-01-08 07:12:47 【问题描述】:

我做了研究,但没有找到这个问题的充分答案。

为什么我们需要比舞台更多的舞台。

一个线程 -> 一个大任务(A,B,C,D) VS CompletableFuture 具有阶段 A、B、C、D

所以我的答案如下:

如果我有更多阶段,我可以将任务拆分为不同的方法和类 如果我有更多阶段,执行与其他整体任务相关的整个任务会更公平。我的意思是什么?假设我们的系统中只有一个线程。如果我以这种方式执行它 -> 一个大任务(A,B,C,D),那么在第一个大任务准备好之后,我的下一个大任务(W,X,Y,Z)就有机会被执行。使用 CompletionStages 更公平:因为 A,W,B,C,X,Y,Z,D 可能是执行顺序

我的最后一点是否有任何指标/规则,我应该将大任务分成多小的子任务?

我的最后一点是针对 CompletableFutures 中的阶段的吗? 我的第一点是不是 CompletableFutures 中的各个阶段的要点? 使用 CompletableFutures 的阶段还有其他要点吗?

【问题讨论】:

假设您有任务 A、B 和 C,其中 A 后面跟着 B 或 C,具体取决于变量 x。现在想象在处理 A 之前没有设置 x。在那里,如果你想在这里异步做事情,你需要阶段。 但是如果我只使用一个 completableFuture 管道,那么在执行 B 和 C 之前计算 x,如果我在一个线程中使用这个“阶段”也是一样的。我没有考虑涉及到复杂的 CompletalbeStages.allOf()/anyOf() 来混合不同的管道;我的意思是一个管道中的正常 thenApplyAsync 方法 我的意思是,您可以同时计算 x 并异步处理 A。然后,根据 x 的结果,将 B 或 C 链接到 A。 是的,我明白,你的意思,不认为我们可以将其添加到我的项目符号中,....但也许还有一些分期和直接分期的原因,所以 cf .thenApplyAsync()..thenApplayAsync()..thenApplyAsync() 在您的情况下,此链将被破坏,并且条件语句将在其中的某个点;那 - 我所说的同意你 - 是使用 CFs 的一个很好的理由^^ 【参考方案1】:

当你有选择的时候,比如

CompletableFuture.supplyAsync(() -> method1())
    .thenApply(o1 -> method2(o1))
    .thenApply(o2 -> method3(o2))
    .thenAccept(o3 -> method4(o3));

CompletableFuture.runAsync(() -> 
    var o1 = method1();
    var o2 = method2(o1);
    var o3 = method3(o2);
    method4(o3);
);

CompletableFuture.runAsync(() -> method4(method3(method2(method1()))));

使用多个阶段没有任何优势。事实上,第一个变体比其他变体更难调试。

当链接不在同一个地方发生时,情况就不同了。想象一个库有一个未来的返回方法,封装像supplyAsync(() -> method1()) 这样的东西,另一个库调用该方法,链接另一个操作并将组合返回给将链接另一个应用程序的应用程序。

只有当函数中调用的方法仍然由每个库的 API 提供并且具有顺序性时,才可能在单个阶段中表达相同的内容,即我们不是在谈论 thenCompose(…) 类型的阶段。

但是这样的链仍然很难调试,而 Loom 项目正试图解决这个问题。然后,您可以将操作表示为调用序列,就像在第二个或第三个变体中一样,即使方法可能会阻塞,但在虚拟线程中运行它,每次阻塞时都会释放底层本机线程。

那么,我们对线性阶段链的使用就更少了。


创建依赖阶段的线性链的剩余用例是具有不同的执行器。例如

CompletableFuture.supplyAsync(() -> fetchFromDb(), MY_BACKGROUND_EXECUTOR)
    .thenAcceptAsync(data -> updateSwingModel(data), EventQueue::invokeLater)
    .whenCompleteAsync((x, thrown) ->
         updateStatusBar(jobID, thrown), EventQueue::invokeLater);

在这里,将操作编写为单个块不是一种选择……

【讨论】:

是的,有不同的执行者,如果忘记了^^,你就在这里.. 是的,Loom 可以改变游戏规则,如果我知道的话,Java 18 将在实验模式下引入 Loom,我猜?直截了当地说:“使用多个阶段没有优势”,我的公平点不是重点吗?如果我把它分成更多的阶段,执行者可以做更多的大任务,如果执行者满了,它不必先完成大任务1、2、3,然后再完成可以从大任务4、5、6开始(3个线程的Executor) 在这种情况下,无法保证执行器会从另一个依赖链中获取任务。事实上,对于默认的执行器,一个 Fork/Join 线程池,这是不太可能的,因为它更喜欢本地排队的任务(即来自其当前执行的依赖链)。如果你想要交错执行,你最好确保池有足够的线程并让抢占式多任务处理完成它的工作。 啊,我明白了,这很好,但如果我只有 cpu 密集型任务,那么我应该选择一个大小与 cpu-core-size 几乎相同的池;如果我采取更多,那么我的表现会更差,因为抢先式多任务处理;是否存在任何其他解决方案,只有 cpu-core 多线程和公平策略? 公平性和最大吞吐量是不同的。如果公平真的是一个问题,不要犹豫,创建比内核更多的线程。是的,切换会降低最大吞吐量,但不要高估影响。其他增加公平性的尝试可能会花费更多而获得更少。

以上是关于CompletableFuture:为啥我们需要阶段?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 CompletableFuture.runAsync() 并不总是提交给 ForkJoinPool.commonPool()?

为啥即使我不调用 get() 或 join(),这个 CompletableFuture 也能工作?

记一次生产中使用CompletableFuture遇到的坑

异步编程利器:CompletableFuture详解

异步编程利器:CompletableFuture如何操作?

今天来认识一个异步编程类CompletableFuture