79 votes

La mise en œuvre de coroutines en Java

Cette question est liée à ma question sur l' existant coroutine implémentations en Java. Si, comme je le soupçonne, il s'avère qu'il n'y a pas de mise en œuvre de coroutines actuellement disponible en Java, ce qui serait nécessaire pour les mettre en œuvre?

Comme je l'ai dit dans la question, je sais que sur les éléments suivants:

  1. Vous pouvez mettre en œuvre "coroutines" en tant que fils/pools de threads derrière les coulisses.
  2. Vous pouvez faire tricksy choses avec bytecode JVM en coulisses pour faire des coroutines possible.
  3. Le soi-disant "Da Vinci" Machine JVM a des primitives qui font de coroutines faisable sans manipulation de bytecode.
  4. Il existe différents JNI-les approches fondées sur des coroutines également possible.

Je vais répondre à chacun des lacunes dans la tour.

Basé sur le Thread coroutines

Cette "solution" est pathologique. Le point de l'ensemble de coroutines est d' éviter la surcharge de filetage, de verrouillage, d'ordonnancement du noyau, etc. Coroutines sont censés être léger et rapide, et à exécuter uniquement dans l'espace utilisateur. Leur mise en œuvre dans des conditions de pleine inclinaison des discussions avec des restrictions strictes de se débarrasser de tous les avantages.

La JVM de manipulation de bytecode

Cette solution est plus pratique, bien qu'un peu difficile à enlever. C'est à peu près la même chose que de sauter vers le bas en langage d'assemblage pour la coroutine bibliothèques en C (qui est la façon dont beaucoup d'entre eux travaillent) avec l'avantage que vous n'avez qu'une architecture à s'inquiéter et obtenir le droit.

Elle a également des liens vous uniquement de l'exécution de votre code entièrement conformes à la JVM des piles (ce qui signifie, par exemple, pas d'Android), sauf si vous pouvez trouver un moyen de faire la même chose sur la non-conformité de la pile. Si vous ne trouver un moyen de le faire, cependant, vous avez doublé votre système de complexité et de besoins de tests.

Le Da Vinci Machine

Le Da Vinci Machine est cool pour l'expérimentation, mais comme il n'est pas un standard de la JVM de ses fonctionnalités ne sont pas disponibles partout. En effet, je soupçonne la plupart des environnements de production en particulier, d'interdire l'utilisation du Da Vinci de la Machine. Ainsi je pourrais l'utiliser pour faire cool expériences, mais pas pour n'importe quel code je m'attends à de la libération pour le monde réel.

Cela a aussi le problème ajouté similaire à la JVM du bytecode à la manipulation de la solution ci-dessus: ne fonctionne pas sur piles de rechange (comme Android).

JNI mise en œuvre

Cette solution rend le point de le faire en Java à tous discutable. Chaque combinaison de PROCESSEUR et système d'exploitation a besoin de tests indépendants et chacun est un point de potentiellement frustrant subtile échec. Sinon, bien sûr, je pourrais m'attacher vers le bas à une plate-forme entièrement, mais cela, aussi, fait le point de faire les choses en Java entièrement discutable.

Alors...

Est-il possible de mettre en œuvre des coroutines en Java sans l'aide de l'un de ces quatre techniques? Ou vais-je être obligé d'utiliser l'un de ces quatre qui sent le moins (JVM manipulation) à la place?


Edité pour ajouter:

Juste pour s'assurer que la confusion est contenue, cela est lié à la question de mon autre, mais pas les mêmes. Que l'on est à la recherche d'un existant, la mise en œuvre, dans une tentative d'éviter de réinventer la roue inutilement. Il s'agit d'une question relative à la façon dont on pourrait aller sur la mise en œuvre de coroutines en Java, l'autre doit prouver sans réplique. Le but est de conserver les différentes questions sur des threads différents.

44voto

luke Points 6688

Je voudrais prendre un coup d'oeil à ceci: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.htmlde sa jolie intéressant et devrait fournir un bon endroit pour commencer. Mais nous sommes bien sûr à l'aide de Java, de sorte que nous pouvons faire mieux (ou peut-être pire, car il n'y a pas de macros :))

De ma compréhension avec les coroutines vous avez généralement un producteur et un consommateur coroutine (ou, au moins, ce est le motif le plus fréquent). Mais sémantiquement vous ne voulez pas le producteur d'appeler le consommateur ou vice-versa, car cela introduit une asymétrie. Mais compte tenu de la façon la pile en fonction des langues de travail nous aurons besoin d'avoir quelqu'un de faire l'appel.

Donc, ici, est un type très simple de la hiérarchie:

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

Maintenant, bien sûr, la partie la plus difficile est la mise en œuvre des interfaces, en particulier, il est difficile de briser un calcul en différentes étapes. Pour cela, vous souhaiterez probablement tout un autre ensemble d' persistante des structures de contrôle. L'idée de base est que nous voulons simuler non-transfert local de contrôle (à la fin de sa un peu comme nous sommes en simulant une goto). Essentiellement, nous voulons déplacer loin de l'aide de la pile, et l' pc (program counter) en conservant l'état de nos opérations en cours dans le tas plutôt que sur la pile. Par conséquent, nous allons avoir besoin d'un tas de classes d'assistance.

Par exemple:

Disons que dans un monde idéal, vous avez voulu écrire un consommateur qui ressemblait à ça (psuedocode):

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

nous avons besoin de l'abstraction de la variable locale comme is_doneet other_state et nous avons besoin pour résumé le tout en boucle elle-même, parce que notre yield comme l'opération ne va pas être à l'aide de la pile. Donc, nous allons créer une boucle while abstraction et les classes associées:

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

L'astuce de Base ici est de déplacer les locaux de variables de classe variables et tourner à la portée des blocs dans les classes qui nous donne la possibilité de "re-enter' notre 'boucle' après rendement de notre valeur de retour.

Pour mettre en œuvre notre producteur

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

Des astuces similaires pourrait être fait pour d'autres base de flux de contrôle des structures. Vous, l'idéal serait de construire une bibliothèque de ces classes d'aide et puis les utiliser pour mettre en œuvre ces interfaces simples qui finiront par vous donner la sémantique de co-routines. Je suis sûr que tout ce que j'ai écrit ici peut être généralisée et étendue considérablement.

4voto

Bunny83 Points 26

Je viens de tomber sur cette question et je veux juste mentionner que je pense qu'il pourrait être possible de mettre en œuvre des coroutines ou des générateurs de manière similaire, C#. Cela dit je n'ai pas vraiment l'utilisation de Java, mais le CIL a assez des limitations semblables comme la JVM a.

Le rendement de la déclaration en C# est une pure fonctionnalité du langage et ne fait pas partie de la CIL du bytecode. Le compilateur C# crée un caché privé de classe pour chaque générateur de fonction. Si vous utilisez l'instruction rendement dans une fonction, elle doit retourner un IEnumerator ou un IEnumerable. Le compilateur "packs" votre code en une statemachine-comme la classe.

Le compilateur C# pourriez utiliser certains "goto" dans le code généré pour la conversion en une statemachine plus facile. Je ne connais pas les capacités de bytecode Java et si il existe quelque chose comme une plaine inconditionnel de sauter, mais au "niveau de l'assemblée", il est généralement possible.

Comme déjà mentionné, cette fonctionnalité doit être mis en œuvre dans le compilateur. Car je n'ai que peu de connaissances sur Java et c'est compilateur, je ne peux pas dire si il est possible de modifier / étendre le compilateur, peut-être avec un "préprocesseur" ou quelque chose.

Personnellement, j'adore les coroutines. Comme une Unité et développeur de jeux je les utilise assez souvent. Parce que je joue beaucoup de Minecraft avec ComputerCraft j'étais curieux de savoir pourquoi les coroutines en Lua (LuaJ) sont mis en œuvre avec les threads.

0voto

Howard Lovatt Points 94

J'ai une Coroutine classe que j'utilise en Java. Il est basé sur les threads et à l'aide de threads a l'avantage de permettre le fonctionnement en parallèle, qui sur des machines peut être un avantage. Par conséquent, vous pourriez envisager un thread approche fondée sur les.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X