This question came up after reading the Loom proposal, which describes an approach of implementing coroutines in the Java programming language.

Particularly this proposal says that to implement this feature in the language, additional JVM support will be required.

据我所知,JVM上已经有几种语言将协同路由作为其功能集的一部分,比如Kotlin和Scala.

那么,在没有额外支持的情况下,该功能是如何实现的?在没有额外支持的情况下,该功能能否高效实现?

推荐答案

tl;dr总结:

Particularly this proposal says that to implement this feature in the language the additional JVM support will be required.

当他们说"必需"时,他们的意思是"必需的,以便以这样一种方式实现,即它在语言之间是可执行的和可互操作的".

那么该功能是如何在没有额外支持的情况下实现的呢

There are many ways, the most easy to understand how it can possibly work (but not necessarily easiest to implement) is to implement your own VM with your own semantics on top of the JVM. (Note that is not how it is actually done, this is only an intuition as to why it can be done.)

and can it be implemented efficiently without it ?

不是真的.

Slightly longer explanation:

Note that one goal of Project Loom is to introduce this abstraction purely as a library. This has three advantages:

  • It is much easier to introduce a new library than it is to change the Java programming language.
  • 在JVM上,用每种语言编写的程序可以立即使用库,而Java语言特性只能由Java程序使用.
  • 可以实现一个具有相同API且不使用新JVM特性的库,这将允许您编写在旧JVM上运行的代码,只需简单的重新编译(尽管性能较低).

然而,将其实现为一个库,排除了聪明的编译器技巧将co routine 转换为其他东西,因为there is no compiler involved.因此,如果没有聪明的编译器技巧,获得良好的性能就要困难得多,这是JVM支持的"要求".

Longer explanation:

In general, all of the usual "powerful" control structures are equivalent in a computational sense and can be implemented using each other.

在那些"强大的"通用控制流 struct 中,最著名的是令人尊敬的GOTO,另一个是延续(Continuations).然后是线程和协程,还有一个人们通常不会想到的,但这也相当于GOTO个:异常.

一种不同的可能性是实例化的调用堆栈,以便该调用堆栈可以作为程序员的对象进行访问,并且可以被修改和重写.(例如,许多Smalltalk方言都这样做,而且这也有点像用C和汇编语言做这件事的方式.)

只要你有one个这样的东西,你就可以拥有all个这样的东西,只要把一个放在另一个上面就行了.

JVM有两个:异常和GOTO,但JVM中的GOTOnot通用的,它非常有限:它只适用于单个方法.(它基本上只用于循环.)所以,这就给我们留下了例外.

因此,这是您问题的一个可能答案:您可以在异常之上实现协同 routine .

另一种可能性是不使用JVM的控制流at all,而是实现自己的堆栈.

然而,这通常不是在JVM上实现协同 routine 时实际采用的路径.最有可能的是,实现协同 routine 的人会 Select 使用蹦床,并将部分执行上下文重新设置为对象.例如,这就是生成器在C语言中的实现方式♯ 在CLI上(不是JVM,但挑战类似).C语言中的生成器(基本上是受限的半共 routine )♯ 通过将方法的局部变量提升到上下文对象的字段中,并在每个yield语句中将该方法拆分为该对象上的多个方法,将它们转换为状态机,并小心地通过上下文对象上的字段将所有状态更改线程化来实现.在async/await作为一种语言特性出现之前,聪明的程序员也用同样的机器实现了异步编程.

这就是你提到的那篇文章最有可能提到的:所有的机器都很昂贵.如果您实现自己的堆栈或将执行上下文提升到一个单独的对象中,或者将所有方法编译成一个giant方法并到处使用GOTO(由于方法的大小限制,这甚至是不可能的),或者使用异常作为控制流,则这两种情况中至少有一种是正确的:

  • 您的调用约定与其他语言所期望的JVM堆栈布局不兼容,即失go interoperability个.
  • The JIT compiler has no idea what the hell your code is doing, and is presented with byte code patterns, execution flow patterns, and usage patterns (e.g. throwing and catching ginormous amounts of exceptions) it doesn't expect and doesn't know how to optimize, i.e. you lose performance.

Rich Hickey(Clojure的设计者)曾在一次演讲中说:"尾部调用、性能、互操作.选两个."我将其概括为我所说的Hickey's Maxim:"高级控制-流、性能、互操作. Select 两个."

In fact, it is generally hard to achieve even one of interop or performance.

Also, your compiler will become more complex.

当构造在JVM中本机可用时,所有这些都消失了.例如,想象一下,如果JVM没有线程.然后,每一种语言实现都会创建自己的线程库,它很难、复杂、缓慢,并且无法与任何other种语言实现的线程库进行互操作.

最近一个真实的例子是lambdas:JVM上的许多语言实现都有lambdas,例如Scala.然后Java也添加了lambdas,但是因为JVM不支持lambdas,它们以某种方式必须是encoded,并且Oracle Select 的编码与Scala以前 Select 的编码不同,这意味着您不能将Java lambda传递给期望Scala Function的Scala方法.这种情况下的解决方案是,Scala开发人员完全重写了他们的lambdas编码,以便与Oracle Select 的编码兼容.这实际上 destruct 了某些地方的兼容性.

Kotlin相关问答推荐

我如何测试一个可组合组件没有显示,但如果它不存在也接受?

无法访问类kotlin.coroutines.CoroutineContext';.判断模块类路径中是否存在丢失或冲突的依赖项

如何进行基于lambda/谓词的两个列表的交集?

我需要后台工作才能使用卡夫卡的消息吗?

在Kotlin中,我是否可以访问已知的WHEN子句值?

如何定义一个函数来接受任何具有特定字段的数据类

房间数据库操作中的协程取消

如何在 Kotlin 中为类方法调用传递变量

为什么 Kotlin 中的 Double 和 Long 类型不推荐直接转换为 Char?

多个不同的指针输入

is return inside function definition 也是 kotlin 中的表达式

java - 如何将函数作为参数从java传递给kotlin方法?

Kotlin 中多个 init 块的用例?

什么是 .kotlin_builtins 文件,我可以从我的 uberjars 中省略它们吗?

如何在 Android Studio 3.1.3 中查看 Kotlin 中有趣的源代码?

如何使用kotlin中的反射查找包中的所有类

Kotlin:如何修改成对的值?

如何在Kotlin中使用ViewModelProviders

Kotlin:使用自定义设置器时没有lateinit的解决方法?

Kotlin - 错误:Could not find or load main class _DefaultPackage