用Java实现协程

这个问题与我在Java中现有协程实现的问题有关。 如果,如我所怀疑的,事实证明,目前在Java中没有完全实现协程,那么实现它们需要什么?

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

  • 你可以在后台实现“协程”作为线程/线程池。
  • 您可以在后台使用JVM字节码做一些棘手的事情,以使协程成为可能。
  • 所谓的“达芬奇机器”JVM实现具有原语,可以使协程无需字节码操作即可完成。
  • 对于协程也有各种基于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();
        }
    }
    

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


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

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


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

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

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

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

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

    链接地址: http://www.djcxy.com/p/53183.html

    上一篇: Implementing coroutines in Java

    下一篇: How do coroutines in Python compare to those in Lua?