用Java实现协程
这个问题与我在Java中的现有协程实现的问题有关。 如果我猜想,现在没有完全实现Java中现有的协程,那么实现它们需要什么?
正如我在这个问题中所说的,我知道以下几点:
- 你可以在后台实现“协程”作为线程/线程池。
- 您可以在后台使用JVM字节码做一些棘手的事情,以使协程成为可能。
- 所谓的“达芬奇机器”JVM实现具有原语,使得协程可以不用字节码操作。
- 有许多基于JNI的协程也是可能的。
我将依次解决每个人的缺陷。
基于线程的协程
这个“解决scheme”是病态的。 协程的整个目的是避免线程,locking,内核调度等开销。协程应该是轻而快的,只能在用户空间中执行。 在全倾斜线程严格限制的条件下实现它们,摆脱了所有的优点。
JVM字节码操作
这个解决scheme更实用,虽然有点难以实现。 这与C语言中的协程库(这是其中的多less个工作)跳到汇编语言中大致相同,其优点是只有一个架构需要担心和正确。
它还将您绑定到只在完全兼容的JVM堆栈上运行代码(这意味着,例如,没有Android),除非您可以find一种方法在不兼容的堆栈上执行相同的操作。 但是,如果您确实find了一种方法,那么您的系统复杂性和testing需求已经翻了一番。
达芬奇机器
达芬奇机器对于实验来说是很酷的,但是由于它不是一个标准的JVM,它的function在任何地方都不可能实现。 实际上我怀疑大多数生产环境都会特别禁止使用达芬奇机器。 因此,我可以用这个来做一些很酷的实验,但是不能用于我想要发布到真实世界的任何代码。
这也有类似上面的JVM字节码操作解决scheme的附加问题:不会在替代堆栈(如Android的)上工作。
JNI的实现
这个解决scheme使得在Java中这样做是毫无意义的。 CPU和操作系统的每个组合都需要独立testing,每个组合都有可能使微妙的失败成为可能。 另外,当然,我可以把自己完全绑定到一个平台上,但是这也使得Java中的事情完全没有意义。
所以…
有没有什么办法可以在没有使用这四种技术的Java中实现协程? 或者我会被迫使用那些味道最less的四个之一(JVM操作)呢?
编辑添加:
只是为了确保混淆,这是我的另一个 相关问题,但不是相同的。 那个正在寻找一个现有的实施,以避免不必要的重新发明车轮。 这个问题涉及到如果另一个certificate是无法回答的,那么如何去执行Java中的协程。 目的是在不同的线程上保留不同的问题。
我会看看这个: http : //www.chiark.greenend.org.uk/~sgtatham/coroutines.html ,它非常有趣,应该提供一个好地方开始。 但是,我们当然使用Java,所以我们可以做得更好(或者更糟,因为没有macros)
根据我对协程的理解,你通常有一个生产者和一个消费者协同程序(或者至less这是最常见的模式)。 但是从语义上讲,你不希望制片人打电话给消费者,反之亦然,因为这会引起不对称。 但是鉴于基于堆栈的语言的工作方式,我们需要有人来打电话。
所以这是一个非常简单的types层次结构:
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_done
和other_state
一样抽象局部variables,并且我们需要抽象出while循环,因为我们的yield
类操作不会使用堆栈。 所以我们来创build一个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(); }
这里的基本技巧是将局部variables移动到类variables,并将范围块转换为类,这使得我们能够在得到返回值后重新input我们的“循环”。
现在来执行我们的制作人
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(); } }
其他基本的控制stream程结构也可以采用类似的技巧。 你最好build立一个这些帮助类的库,然后用它们来实现这些简单的接口,最终会给你的协同例程的语义。 我敢肯定,我在这里写的所有内容都可以概括和扩展。
我刚刚遇到这个问题,只是想提一下,我认为可能有可能以类似C#的方式来实现协程或生成器。 这就是说我实际上并没有使用Java,但是CIL与JVM具有非常类似的限制。
C#中的yield语句是纯语言function,不是CIL字节码的一部分。 C#编译器只是为每个生成器函数创build一个隐藏的私有类。 如果在函数中使用yield语句,则必须返回IEnumerator或IEnumerable。 编译器将你的代码“打包”成一个类似于状态机的类。
C#编译器可能会在生成的代码中使用一些“goto's”来使转换成状态机变得更容易。 我不知道Java字节码的function,如果有一个明显的无条件跳转的东西,但在“组件级”通常是可能的。
如前所述,这个特性必须在编译器中实现。 因为我对Java只有很less的了解,而且它是编译器,所以我不知道是否有可能改变/扩展编译器,也许是用“预处理器”或其他东西。
我个人喜欢协程。 作为一个Unity游戏开发者,我经常使用它们。 因为我用ComputerCraft玩Minecraft很多,我很好奇为什么在Lua(LuaJ)中的协程是用线程来实现的。
我build议在JVM上看看Kotlin协同程序 。 不过,它属于不同的类别。 不涉及任何字节码操作,它也适用于Android。 但是,您将不得不在Kotlin中编写协程。 好处在于Kotlin的devise是为了与Java的互操作性而devise的,所以你仍然可以继续使用你所有的Java库,并且可以在同一个项目中自由地组合Kotlin和Java代码,甚至可以将它们放在同一个目录中,包。
这个kotlinx.coroutines指南提供了更多的例子,而协程devise文档解释了所有的动机,用例和实现细节。
Kotlin使用以下方法进行协同程序
(来自https://kotlinlang.org/docs/reference/coroutines.html ):
协程完全通过编译技术来实现(不需要VM或OS方面的支持),并且通过代码转换来实现暂停。 基本上,每个暂停function(优化可能适用,但我们不会在这里进入)转换为状态机,其中状态对应于挂起的呼叫。 在暂停之前,下一个状态与相关的局部variables等一起被存储在编译器生成的类的字段中。当恢复该协程时,局部variables被恢复并且状态机从暂停之后的状态继续。
暂停的协程可以作为保持暂停状态和当地状态的对象存储和传递。 这种对象的types是Continuation,这里描述的整体代码转换对应于传统的Continuation-passing风格。 因此,暂停function需要额外的参数,types为Continuation。
在https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md查看devise文档;
我有一个我在Java中使用的协程类。 它基于线程和使用线程的优点是允许并行操作,在多核机器上可以是一个优势。 所以你可能要考虑一个基于线程的方法。