用Java实现协程

这个问题与我在Java中的现有协程实现的问题有关。 如果我猜想,现在没有完全实现Java中现有的协程,那么实现它们需要什么?

正如我在这个问题中所说的,我知道以下几点:

  1. 你可以在后台实现“协程”作为线程/线程池。
  2. 您可以在后台使用JVM字节码做一些棘手的事情,以使协程成为可能。
  3. 所谓的“达芬奇机器”JVM实现具有原语,使得协程可以不用字节码操作。
  4. 有许多基于JNI的协程也是可能的。

我将依次解决每个人的缺陷。

基于线程的协程

这个“解决方案”是病态的。 协程的整个目的是避免线程,锁定,内核调度等开销。协程应该是轻快的,只能在用户空间中执行。 在全倾斜线程严格限制的条件下实现它们,摆脱了所有的优点。

JVM字节码操作

这个解决方案更实用,虽然有点难以实现。 这与C语言中的协程库(这是其中的多少个工作)跳到汇编语言中大致相同,其优点是只有一个架构需要担心和正确。

它还将您绑定到只在完全兼容的JVM堆栈上运行代码(这意味着,例如,没有Android),除非您可以找到一种方法在不兼容的堆栈上执行相同的操作。 但是,如果您确实找到了一种方法,那么现在您的系统复杂性和测试需求已经翻了一番。

达芬奇机器

达芬奇机器对于实验来说是很酷的,但是由于它不是一个标准的JVM,它的功能在任何地方都不可能实现。 事实上,我怀疑大多数生产环境都会特别禁止使用达芬奇机器。 因此,我可以用这个来做一些很酷的实验,但是不能用于我想要发布到真实世界的任何代码。

这也有类似上面的JVM字节码操作解决方案的附加问题:不会在替代堆栈(如Android的)上工作。

JNI的实现

这个解决方案使得在Java中这样做是毫无意义的。 CPU和操作系统的每个组合都需要独立测试,每个组合都有可能使微妙的失败成为可能。 另外,当然,我可以把自己完全绑定到一个平台上,但是这也使得Java中的事情完全没有意义。

所以…

有没有什么办法可以在没有使用这四种技术的Java中实现协程? 或者我会被迫使用那些味道最少的四个之一(JVM操作)呢?


编辑添加:

只是为了确保混淆,这是我的另一个 相关问题,但不是相同的。 那个正在寻找一个现有的实施,以避免不必要的重新发明车轮。 这个问题是关于如果另一个证明无法回答的话,如何去执行Java中的协程。 目的是在不同的线程上保留不同的问题。

我会看看这个: http : //www.chiark.greenend.org.uk/~sgtatham/coroutines.html ,它非常有趣,应该提供一个好地方开始。 但是,我们当然使用Java,所以我们可以做得更好(或者更糟,因为没有宏)

根据我对协程的理解,你通常有一个生产者和一个消费者协同程序(或者至少这是最常见的模式)。 但是从语义上讲,你不希望制片人打电话给消费者,反之亦然,因为这会引起不对称。 但是鉴于基于堆栈的语言的工作方式,我们需要有人来打电话。

所以这是一个非常简单的类型层次结构:

public interface CoroutineProducer<T> { public T Produce(); public boolean isDone(); } public interface CoroutineConsumer<T> { public void Consume(T t); } public class CoroutineManager { public static Execute<T>(CoroutineProducer<T> prod, CoroutineConsumer<T> con) { while(!prod.IsDone()) // really simple { T d = prod.Produce(); con.Consume(d); } } } 

当然,难点在于实现接口,特别是难以将计算分解成单独的步骤。 为此,您可能需要一整套其他持久控制结构 。 其基本思想是我们要模拟非本地控制转移(最后它有点像我们模拟goto )。 我们基本上希望通过将当前操作的状态保持在堆而不是在堆栈pc使用堆栈和pc (程序计数器)。 因此,我们将需要一堆辅助类。

例如:

假设在一个理想的世界中,你想写一个看起来像这样的消费者(psuedocode):

 boolean is_done; int other_state; while(!is_done) { //read input //parse input //yield input to coroutine //update is_done and other_state; } 

我们需要像is_doneother_state一样抽象局部变量,并且我们需要抽象出while循环,因为我们的yield类操作不会使用堆栈。 所以我们来创建一个while循环抽象和关联的类:

 enum WhileState {BREAK, CONTINUE, YIELD} abstract class WhileLoop<T> { private boolean is_done; public boolean isDone() { return is_done;} private T rval; public T getReturnValue() {return rval;} protected void setReturnValue(T val) { rval = val; } public T loop() { while(true) { WhileState state = execute(); if(state == WhileState.YIELD) return getReturnValue(); else if(state == WhileState.BREAK) { is_done = true; return null; } } } protected abstract WhileState execute(); } 

这里的基本技巧是将局部变量移动到变量,并将范围块转换为类,这使得我们能够在得到返回值后重新输入我们的“循环”。

现在来执行我们的制作人

 public class SampleProducer : CoroutineProducer<Object> { private WhileLoop<Object> loop;//our control structures become state!! public SampleProducer() { loop = new WhileLoop() { private int other_state;//our local variables become state of the control structure protected WhileState execute() { //this implements a single iteration of the loop if(is_done) return WhileState.BREAK; //read input //parse input Object calcluated_value = ...; //update is_done, figure out if we want to continue setReturnValue(calculated_value); return WhileState.YIELD; } }; } public Object Produce() { Object val = loop.loop(); return val; } public boolean isDone() { //we are done when the loop has exited return loop.isDone(); } } 

其他基本的控制流程结构也可以采用类似的技巧。 你最好建立一个这些帮助类的库,然后用它们来实现这些简单的接口,最终会给你一个联合例程的语义。 我敢肯定,我在这里写的所有内容都可以概括和扩展。

我刚刚遇到这个问题,只是想提一下,我认为可能有可能以类似C#的方式来实现协程或生成器。 这就是说我实际上并没有使用Java,但是CIL与JVM具有非常类似的限制。

C#中的yield语句是纯语言功能,不是CIL字节码的一部分。 C#编译器只是为每个生成器函数创建一个隐藏的私有类。 如果在函数中使用yield语句,则必须返回IEnumerator或IEnumerable。 编译器将你的代码“打包”成一个类似于状态机的类。

C#编译器可能会在生成的代码中使用一些“goto's”来使转换成状态机变得更容易。 我不知道Java字节码的功能,如果有一个明显的无条件跳转的东西,但在“组件级”通常是可能的。

如前所述,这个特性必须在编译器中实现。 因为我对Java只有很少的了解,而且它是编译器,所以我不知道是否有可能改变/扩展编译器,也许是用“预处理器”或其他东西。

我个人喜欢协程。 作为一个Unity游戏开发者,我经常使用它们。 因为我用ComputerCraft玩Minecraft很多,我很好奇为什么在Lua(LuaJ)中的协程是用线程来实现的。

我建议在JVM上看看Kotlin协同程序 。 不过,它属于不同的类别。 不涉及任何字节码操作,它也适用于Android。 但是,您将不得不在Kotlin中编写协程。 好处在于Kotlin的设计是为了与Java的互操作性而设计的,所以你仍然可以继续使用你所有的Java库,并且可以在同一个项目中自由地组合Kotlin和Java代码,甚至可以将它们放在同一个目录中,包。

这个kotlinx.coroutines指南提供了更多的例子,而协程设计文档解释了所有的动机,用例和实现细节。

Kotlin使用以下方法进行协同程序
(来自https://kotlinlang.org/docs/reference/coroutines.html ):

协程完全通过编译技术来实现(不需要VM或OS方面的支持),并且通过代码转换来实现暂停。 基本上,每个暂停功能(优化可能适用,但我们不会在这里进入)转换为状态机,其中状态对应于暂停调用。 在暂停之前,下一个状态与相关的局部变量等一起被存储在编译器生成的类的字段中。当恢复该协程时,局部变量被恢复并且状态机从暂停之后的状态继续。

暂停的协程可以作为保持暂停状态和当地状态的对象存储和传递。 这种对象的类型是Continuation,这里描述的整体代码转换对应于传统的Continuation-passing风格。 因此,暂停功能需要额外的参数,类型为Continuation。

https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md查看设计文&#x6863;

我有一个我在Java中使用的协程类。 它基于线程和使用线程的优点是允许并行操作,在多核机器上可以是一个优势。 所以你可能要考虑一个基于线程的方法。