Project Loom:Reactive模型和协程进行时(翻译)
Java 15将发布Project Loom的第一个版本。我相信这将改变JVM。在这篇文章中,我想深入探讨一下导致我相信这一点的原因。
首先,我们需要了解核心问题。然后,我将尝试描述以前的技术如何解决它。之后,我们将看到Project Loom采取的方法。最后,我将推断后者可能对生态系统产生什么影响。
Project Loom
我们首先必须记住,很长一段时间以来,计算机只有一个内核。即使这样,还是需要同时运行多个程序:这至少要运行两个操作系统和适当的程序。
为了实现并行性的幻觉,它依赖于一个技巧。它运行一个程序,如果该程序没有在特定的时间范围内完成,它将存储其状态以供以后使用。然后,它运行下一个要运行的程序。有几种算法可用来调度下一个程序:循环调度,加权循环调度等。好处是,所有这些都可以通过操作系统线程的概念很好地从开发人员中抽象出来。该操作系统通吃繁重的护理,包括存储执行的状态。但是,线程有两个缺点:
- 线程很重,因为它带有很多状态
- 线程需要大量的机器资源来创建
在所有情况下,现代OS都允许M个线程,其中M是数千个线程。现代机器是多核的,提供N个核,比M少两个数量级。拥有多个内核(无论是物理内核还是虚拟内核)并不会从根本上改变底层机制:M远高于N,并且OS负责从线程到内核的映射。
阻塞线程
面的模型在传统方案中效果很好,但在网络方案中效果不佳。假设有一个Web服务器需要响应HTTP请求。在过去不太好的时候,CGI是处理请求的一种方法。它将每个请求映射到一个流程,以便处理创建整个新流程所需的请求,并在发送响应后将其清除。
Java EE应用程序服务器极大地改善了这种情况,因为实现将线程保留在池中以供以后重用。但是,想象一下响应的生成需要时间,例如因为它需要访问数据库以读取数据。在数据库返回数据之前,线程需要等待。
这意味着线程实际上正在等待其生命周期的大部分时间。一方面,此类线程自己不使用任何CPU。另一方面,它使用其他种类的资源,尤其是内存。
同样,太多线程是操作系统的负担:操作系统必须在数量有限的CPU内核上平衡大量线程。这花费了宝贵的CPU周期,因此,操作系统正在与应用程序竞争CPU。
现在,并发请求的数量可以远远超过服务器可用的线程数量。因此,阻塞的线程浪费资源,并使服务器无响应。为了解决这个问题,可以添加更多的Web服务器来处理负载:这是水平缩放。
在大多数情况下,水平缩放就足够了。等待阻塞线程所花费的时间是浪费的,但是没有任何相关的费用...除非一个人的基础架构位于'云'中。在那种情况下,人们要为未使用的资源付费:这绝不是一个明智的主意。
单线程,反应式和Kotlin协程模型
对于开发人员来说,管理多个线程很复杂。例如,如果您一直在使用(或开发)Swing应用程序,则可能知道“灰色矩形效果”。当用户与窗口的交互(例如单击)启动长时间运行的任务时,就会发生这种情况。如果将另一个窗口移到Swing窗口上,然后再移出,Swing不会重画另一个窗口与Swing窗口相交的区域,而不会留下难看的灰色矩形。原因是长时间运行的任务是在“ 事件调度线程”上启动的,而不是在专用线程上启动的。而且这很容易避免,甚至不涉及在共享的可变状态上进行同步!
为了避免这种情况,某些堆栈完全禁止开发人员使用多个线程。例如,Node.js的API仅提供一个非阻塞事件循环线程:提交的函数采用回调的形式。请注意,这不会阻止实现使用多个线程。
该反应的方法是另一种选择,其实颇为相似。尽管它摆脱了单线程API的限制,并提供了反压力机制,但它仍然需要非阻塞代码。由于OS线程很昂贵,因此Reactive将它们池化,并在整个应用程序生命周期中重复使用它们。核心过程是从池中获取空闲线程,让其执行代码,然后将线程释放回池中。这就是为什么它需要非阻塞代码的原因:如果代码阻塞了,那么执行线程将不会被释放,并且池将在某一点或另一点耗尽。
我感兴趣地观察了Reactive模型如何像篝火一样在Spring生态系统中传播,尽管我选择站在一边。恕我直言,反应式有几个缺点:
- 编写(和阅读!)反应式代码的思维方式与编写传统代码的思维方式非常不同。我愿意承认改变心态只需要时间,持续时间取决于每个开发人员。
- 尽管真正的开发人员不会调试,但我知道很多人会调试-包括我自己。由于上述线程切换的魔力,要跟踪一段代码及其相关的状态并不容易。这需要足够的工具,例如带有相关插件的 IntelliJ IDEA 。
- 最后,出于相同的原因,传统的堆栈跟踪也无济于事。一些黑魔法可以绕开它。但是,这不是为了胆小者。有关选项的完整列表,请查看此文档。
Kotlin语言提供了Reactive方法的替代方法:协程。简而言之,当使用suspend关键字时,Kotlin编译器会在字节码中生成一个有限状态机。好处是在协程块中调用的函数看起来像是顺序执行的,尽管它们是并行执行的-更确切地说,取决于确切的范围,有可能会执行。
Project Loom和虚拟线程
Reactive模型和Kotlin协程都在客户端代码和JVM线程之间添加了一个额外的抽象层。框架/库的职责是动态地将一个映射到另一个。问题的症结在于JVM线程是OS线程的薄包装:请记住,OS线程创建起来很昂贵,并且数量限制为数千个。
Project Loom的目标是实际上将JVM线程与OS线程解耦。
当我第一次意识到该倡议时,其想法是创建一个称为Fiber(线程,Project Loom,您能抓住麻烦吗?)的抽象。一个Fiber责任是让一个操作系统线程,使其运行代码,释放回池,就像无栈一样。
当前的建议有很大的不同:Fiber它没有使用新的类,而是重新使用了Java开发人员非常熟悉的一个类- java.lang.Thread!
因此,在新的JVM版本中,某些Thread对象可能是重量级的并映射到OS线程,而另一些对象可能是虚拟线程。
Project Loom发布的后续影响
主要问题是,既然JVM API提供了对OS线程的抽象,那么其他抽象(例如响应式和协程)又会变成什么样呢?我对预测不满意,但以下是Reactive /协程背后的公司可能采取的一些态度:
- 正面态度,他们意识到自己的框架不再带来任何附加值,而只是重复。他们停止了开发工作,仅向现有客户提供维护版本。他们帮助说客户迁移到新的ThreadAPI,一些帮助可能是以付费咨询的形式。
- 反面态度,他们在各自的框架中投入了大量的精力之后,他们决定继续进行,好像什么也没有发生。例如,Spring框架负责实际设计一个共享的Reactive API,称为Reactive Streams,没有Spring依赖项。当前有两种实现,RxJava v2和Pivotal的Project Reactor。另一方面,JetBrains宣传Kotlin的协程是并行运行代码的最简单方法。
- 中间态度。这两个框架都将继续其生命,但是会将它们各自的基础实现更改为使用虚拟线程。
由于沉没成本的谬误,排在第一位的可能性极小:销售和市场营销将努力保持其“竞争优势”-无论在他们眼中意味着什么。尽管有些工程师出于相同的原因希望保留现有代码,但其他一些工程师则将努力使用新的API。因此,我也不相信第二名也会发生。但是,我认为这两个工程派之间都发挥着力量,然后它们与市场营销/销售之间将在#3之间找到平衡。
结论
Project Looms将现有的Thread实现方式从OS线程的映射更改为可以表示此类线程或虚拟线程的抽象。就其本身而言,这是一个有趣的举动,它在一个平台上历来比创新更重视向后兼容性。与其他最新的Java版本相比,此功能是真正的游戏规则改变者。一般而言,开发人员应尽快开始熟悉它。打算学习Reactive和协程的开发人员可能应该退后一步,并评估他们是否应该学习新的ThreadAPI- 是否需要。
翻译原文
https://blog.frankel.ch/project-loom-reactive-coroutines/
扩展阅读
Project Loom地址: https://github.com/openjdk/loom
Project Loom现有状态: http://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.html
Project Loom:Reactive模型和协程进行时(翻译)的更多相关文章
- Unity 协程运行时的监控和优化
我是快乐的搬运工: http://gulu-dev.com/post/perf_assist/2016-12-20-unity-coroutine-optimizing#toc_0 --------- ...
- 闭包(closure)与协程共用时要注意的事情
闭包是一种能够让你用非常舒服的方式来编程的小技巧,Go也支持闭包. 假设从来没有接触过闭包,想在一開始就弄懂什么是闭包(closure)是非常困难的,就像递归一样,直到你真正写过.用过它,你才干真正的 ...
- 终结python协程----从yield到actor模型的实现
把应用程序的代码分为多个代码块,正常情况代码自上而下顺序执行.如果代码块A运行过程中,能够切换执行代码块B,又能够从代码块B再切换回去继续执行代码块A,这就实现了协程 我们知道线程的调度(线程上下文切 ...
- 图解协程调度模型-GMP模型
现在无论是客户端.服务端或web开发都会涉及到多线程的概念.那么大家也知道,线程是操作系统能够进行运算调度的最小单位,同一个进程中的多个线程都共享这个进程的全部系统资源. 线程 三个基本概念 内核线程 ...
- Java协程实践指南(一)
一. 协程产生的背景 说起协程,大多数人的第一印象可能就是GoLang,这也是Go语言非常吸引人的地方之一,它内建的并发支持.Go语言并发体系的理论是C.A.R Hoare在1978年提出的CSP(C ...
- 从Erlang进程看协程思想
从Erlang进程看协程思想 多核慢慢火了以后,协程类编程也开始越来越火了.比较有代表性的有Go的goroutine.Erlang的Erlang进程.Scala的actor.windows下的fibr ...
- 第十天 多进程、协程(multiprocessing、greenlet、gevent、gevent.monkey、select、selector)
1.多进程实现方式(类似于多线程) import multiprocessing import time,threading def thread_run():#定义一个线程函数 print(&quo ...
- 协程coroutine
协程(coroutine)顾名思义就是“协作的例程”(co-operative routines).跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻辑上类似多任务的编程 ...
- day-5 python协程与I/O编程深入浅出
基于python编程语言环境,重新学习了一遍操作系统IO编程基本知识,同时也学习了什么是协程,通过实际编程,了解进程+协程的优势. 一.python协程编程实现 1. 什么是协程(以下内容来自维基百 ...
随机推荐
- 阿里云服务器centOS安装Docker
环境准备 1.需要有Linux的基础 2.centOS 7 环境查看 # 系统内核是 3.10 以上的 [root@iz2zeaet7s13lfkc8r3e2kz ~]# uname -r -.el7 ...
- Java中的集合(四)PriorityQueue常用方法
Java中的集合(四)PriorityQueue常用方法 PriorityQueue的基本概念等都在上一篇已说明,感兴趣的可以点击 Java中的集合(三)继承Collection的Queue接口 查看 ...
- HFish开源蜜罐搭建
简介 Hfish是一款开源的蜜罐,包含了多种仿真服务,如:redis.ssh.telnet.web服务等,支持单机部署.docker部署.集群部署等形式.不属于高交互蜜罐的范畴,只是用来体验一把.放在 ...
- Java Word中的文本、图片替换功能
Word中的替换功能以查找指定文本然后替换为新的文本,可单个替换或全部替换.以下将要介绍的内容,除常见的以文本替换文本外,还将介绍使用不同对象进行替换的方法,具体可包括: 1. 指定字符串内容替换文本 ...
- 【Socket通信】关于Socket通信原理解析及python实现
Socket(套接字)通信{网络通信其实就是Socket间的通信},首先了解下概念:[来源于百度百科] "两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket.& ...
- [源码解析]为什么mapPartition比map更高效
[源码解析]为什么mapPartition比map更高效 目录 [源码解析]为什么mapPartition比map更高效 0x00 摘要 0x01 map vs mapPartition 1.1 ma ...
- 【Checkpoint】HA模式下结合zookeeper说一下checkpoint流程
checkpoint过程 配置了HA的HDFS中,有active和standby namenode两个namenode节点.他们的内存中保存了一样的集群元数据信息,这个后续我会详细用一篇文章介绍HA, ...
- Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask
hive运行查询语句时报错: Error: org.apache.hive.service.cli.HiveSQLException: Error while processing statement ...
- Java实现 LeetCode 823 带因子的二叉树(DP)
823. 带因子的二叉树 给出一个含有不重复整数元素的数组,每个整数均大于 1. 我们用这些整数来构建二叉树,每个整数可以使用任意次数. 其中:每个非叶结点的值应等于它的两个子结点的值的乘积. 满足条 ...
- Java实现 LeetCode 284 顶端迭代器
284. 顶端迭代器 给定一个迭代器类的接口,接口包含两个方法: next() 和 hasNext().设计并实现一个支持 peek() 操作的顶端迭代器 – 其本质就是把原本应由 next() 方法 ...