Ash*_*win 21 java multithreading asynchronous project-loom java-loom
在这里提供一些背景信息,我一直在关注项目织机一段时间。我看过织机的状态。我做过异步编程。
异步编程(由 java nio 提供)在任务等待时将线程返回到线程池,并且它竭尽全力不阻塞线程。这带来了很大的性能提升,我们现在可以处理更多的请求,因为它们不受操作系统线程数量的直接限制。但我们在这里失去的是上下文。同一个任务现在不仅仅与一个线程相关联。一旦我们将任务与线程分离,所有上下文都将丢失。异常跟踪不提供非常有用的信息并且调试很困难。
随之而来的项目织机virtual threads
成为单一的并发单元。现在您可以在单个virtual thread
.
直到现在一切都很好,但文章继续指出,项目织机:
一个简单的、同步的 Web 服务器将能够处理更多的请求,而无需更多的硬件。
我不明白我们如何通过异步 API 的项目机获得性能优势?在asynchrounous APIs
确保不要保留任何线程空闲。那么,项目织机如何使其比asynchronous
API更高效和高性能?
让我重新表述这个问题。假设我们有一个 http 服务器,它接收请求并使用支持的持久数据库执行一些 crud 操作。比如说,这个 http 服务器处理了很多请求 - 100K RPM。两种实现方式:
virtual threads
为每个请求生成。如果有IO,虚拟线程只是等待任务完成。然后返回 HTTP 响应。基本上,没有针对virtual threads
.鉴于硬件和吞吐量保持不变,在响应时间或处理更多吞吐量方面,任何一种解决方案会比另一种更好吗?
我的猜测是,与性能没有任何区别。
tal*_*lex 11
我们没有从异步 API 中获益。我们可能会获得类似于异步的性能,但使用同步代码。
Jat*_*tin 10
@talex 的回答说得很清楚。进一步补充一下。
Loom 更多的是关于本机并发抽象,它还有助于编写异步代码。考虑到它是虚拟机级别的抽象,而不仅仅是代码级别(就像我们到目前为止所做的那样CompletableFuture
),它允许人们实现异步行为,但使用减少样板。
对于 Loom,更强大的抽象是救世主。我们已经多次看到这种语法糖的抽象如何使人有效地编写程序。无论是 JDK8 中的 FunctionInterfaces,还是 Scala 中的 for 推导式。
使用 loom,不需要链接多个 CompletableFuture(以节省资源)。但可以同步编写代码。当遇到每个阻塞操作(ReentrantLock、i/o、JDBC 调用)时,虚拟线程就会停止。而且因为这些是轻量级线程,所以上下文切换要便宜得多,这与内核线程不同。
当被阻塞时,实际的承载线程(正在运行run
虚拟线程的主体)将开始执行其他虚拟线程的运行。因此,载体线程实际上并没有闲置,而是在执行其他一些工作。每当未停放时,都会返回继续执行原始虚拟线程。就像线程池的工作方式一样。但在这里,您有一个单一的载体线程,以某种方式执行多个虚拟线程的主体,在阻塞时从一个虚拟线程切换到另一个虚拟线程。
我们获得了与手动编写的异步代码相同的行为(以及因此的性能),但避免了样板板做同样的事情。
考虑 Web 框架的情况,其中有一个单独的线程池来处理 I/O,另一个线程池用于执行 http 请求。对于简单的 HTTP 请求,可以从 http 池线程本身来处理请求。但是,如果存在任何阻塞(或)高 CPU 操作,我们会让此活动在单独的线程上异步发生。
该线程将从传入请求中收集信息,生成 a CompletableFuture
,并将其与管道链接(作为一个阶段从数据库读取,然后从中进行计算,然后是另一个阶段写回数据库案例、Web 服务调用等) 。每一个都是一个阶段,结果CompletablFuture
返回到网络框架。
当最终的 future 完成时,网络框架使用结果转发回客户端。这就是Play-Framework
其他人一直在处理的方式。在 http 线程处理池和每个请求的执行之间提供隔离。但如果我们更深入地研究这个问题,我们为什么要这样做呢?
一个核心原因是有效利用资源。特别是拦截来电。因此,我们与thenApply
etc 链接起来,这样任何活动上都不会阻塞任何线程,并且我们可以用更少的线程做更多的事情。
这很好用,但相当冗长。调试确实很痛苦,如果其中一个中间阶段出现异常,控制流就会陷入混乱,导致需要更多代码来处理它。
使用 Loom,我们编写同步代码,并让其他人决定在阻塞时做什么。而不是睡觉什么也不做。