Реализация сопрограмм в Java

Этот вопрос связан с моим вопросом на существующих реализациях сопрограммы в Java. Если, поскольку я подозреваю, оказывается, что нет никакого полного внедрения сопрограмм, в настоящее время доступных в Java, что потребовалось бы, чтобы реализовывать их?

Как я сказал в том вопросе, я знаю о следующем:

  1. Можно реализовать "сопрограммы" как потоки/пулы потоков негласно.
  2. Можно сделать игривые вещи с байт-кодом JVM негласно для создания сопрограмм возможными.
  3. Так называемая "Машина Da Vinci" реализация JVM имеет примитивы, которые делают сопрограммы выполнимыми без управления байт-кодом.
  4. Существуют различные основанные на JNI подходы к сопрограммам, также возможным.

Я обращусь к каждому дефициты в свою очередь.

Основанные на потоке сопрограммы

Это "решение" является патологическим. Смысл сопрограмм должен избежать издержек поточной обработки, блокировки, планирования ядра, и т.д. Сопрограммы, как предполагается, легки и быстры и выполняются только в пространстве пользователя. Реализация их с точки зрения потоков полного наклона с трудными ограничениями избавляется от всех преимуществ.

Управление байт-кодом JVM

Это решение более практично, хотя немного трудный для осуществления. Это - примерно то же как спрыгивающий в ассемблер для библиотек сопрограммы в C (который является, сколько из них работает) с преимуществом, о котором у Вас есть только одна архитектура, чтобы волноваться и разобраться.

Это также привязывает Вас только к выполнению Вашего кода полностью совместимых стопок JVM (что не означает, например, Android), если Вы не можете найти способ сделать то же самое на несовместимом стеке. При нахождении способа сделать это, однако, Вы теперь удвоили свою сложность системы и тестирующие потребности.

Машина Da Vinci

Машина Da Vinci является классной для экспериментирования, но так как это не стандартная JVM, его функции не будут доступными везде. Действительно я подозреваю, что большинство продуктивных сред конкретно запретило бы использование Машины Da Vinci. Таким образом я мог использовать это для создания прохладных экспериментов, но не для любого кода, который я ожидаю выпускать к реальному миру.

Это также имеет добавленную проблему, подобную решению для управления байт-кодом JVM выше: не будет работать над альтернативными стеками (как Android).

Реализация JNI

Это решение представляет точку выполнения, это в Java вообще обсуждает. Каждая комбинация ЦП и операционной системы требует независимого тестирования, и каждый - точка потенциально расстраивающего тонкого отказа. С другой стороны, конечно, я мог привязать меня к одной платформе полностью, но это также высказывает мнение выполнения вещей в совершенно спорном Java.

Так...

Там какой-либо путь состоит в том, чтобы реализовать сопрограммы в Java, не используя один из этих четырех методов? Или я буду вынужден использовать тот из тех четырех, который чувствует запах наименьшего количества (управление JVM) вместо этого?


Отредактированный для добавления:

Только, чтобы гарантировать, что беспорядок содержится, это - связанный вопрос моему другому, но не то же. Тот ищет существующую реализацию в попытке постараться не изобретать велосипед излишне. Этот - вопрос, касающийся, как можно было бы пойти о реализации сопрограмм в Java, должен другой оказаться не имеющим ответа. Намерение состоит в том, чтобы сохранить различные вопросы на различных потоках.

63
задан Marko Topolnik 19 March 2019 в 07:50
поделиться

1 ответ

Я бы взглянул на это: 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 (счетчик программ), сохраняя состояние наших текущих операций в куче, а не в стеке. Поэтому нам понадобится куча вспомогательных классов.

Например:

Предположим, что в идеальном мире вы хотели бы написать потребителя, который выглядел бы так (псевдокод):

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 , и нам нужно абстрагироваться от самого цикла 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();
}

Основной трюк заключается в том, чтобы переместить локальные переменные в переменные class и превратить блоки области видимости в классы, что дает нам возможность чтобы «повторно войти» в наш «цикл» после получения возвращаемого значения.

Теперь реализуем нашего производителя.

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();
    }
}

Аналогичные трюки можно проделать и с другими базовыми структурами потока управления. В идеале вы должны создать библиотеку этих вспомогательных классов, а затем использовать их для реализации этих простых интерфейсов, которые в конечном итоге дадут вам семантику совместных подпрограмм. Я уверен, что все, что я здесь написал, можно значительно обобщить и расширить.

35
ответ дан 24 November 2019 в 16:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: