1. ホーム
  2. java

[解決済み] Javaでコルーチンを実装する

2022-03-09 23:22:45

質問

この質問は、私の質問と関連しています。 Javaにおける既存のコルーチンの実装 . もし、私が思うに、現在Javaにコルーチンの完全な実装がないことが判明した場合、それを実装するためには何が必要でしょうか?

その質問で申し上げたように、私は以下のことを知っています。

  1. コルーチン("coroutine")を裏側のスレッド/スレッドプールとして実装することができます。
  2. コルーチンを可能にするために、裏でJVMバイトコードでトリック的なことをすることができます。
  3. いわゆるダ・ヴィンチ・マシンと呼ばれるJVM実装は、コルーチンを無人で実行するためのプリミティブを持っています。 バイトコード操作
  4. また、JNIベースの様々なコルーチンへのアプローチも可能です。

それぞれの欠点を順を追って説明します。

スレッドベースのコルーチン

この解決策は病的です。 コルーチンの要点は 避ける スレッド、ロック、カーネルスケジューリングなどのオーバーヘッドが発生します。 コルーチンは軽量で高速であり、ユーザ空間のみで実行されることが前提となっています。 コルーチンを、厳しい制約のある本格的なスレッドとして実装すると、その利点がすべて失われてしまいます。

JVMバイトコード操作

この解決策は、少し難しいですが、より実用的です。 これは、C言語のコルーチンライブラリのためにアセンブリ言語に飛び降りるのとほぼ同じです(多くのコルーチンライブラリはこの方法で動作しています)が、心配するのは1つのアーキテクチャだけで、正しく動作させることができるという利点があります。

また、非準拠のスタックで同じことをする方法が見つからない限り、完全に準拠したJVMスタック(つまり、たとえばAndroidはダメ)でのみコードを実行するように縛られることになります。 しかし、これを実現する方法を見つけたとしても、システムの複雑さとテストの必要性が倍増してしまいます。

ダ・ヴィンチ・マシン

ダ・ヴィンチ・マシンは実験用としてはクールですが、標準的なJVMではないので、その機能はどこでも使えるというわけではありません。 実際、ほとんどの生産環境では、ダ・ヴィンチ・マシンの使用は特に禁止されていると思います。 ですから、私はこのマシンを使ってクールな実験をすることはできますが、実世界にリリースすることを想定したコードには使えません。

また、上記のJVMバイトコード操作のソリューションと同様の問題があります。

JNIの実装

この解決策は、Javaでこれを行う意味を全く無くしてしまいます。 CPUとOSの組み合わせごとに独立したテストが必要で、それぞれ微妙に失敗してイライラする可能性がある点です。 あるいは、もちろん、完全に1つのプラットフォームに自分を縛ることもできますが、これも、Javaで物事を行う意味を全く無意味なものにしてしまいます。

それで...

この4つのテクニックのどれかを使わずに、Javaでコルーチンを実装する方法はないのでしょうか? それとも、この4つのうち最も臭いの少ないもの(JVM操作)を代わりに使わざるを得ないのでしょうか?


追加で編集しました。

混乱を収束させるために、これは 関連 に対する質問 私のもう一つの が、同じではありません。 その1つは 既存の を実装し、不必要に車輪の再発明をしないようにするためです。 この質問は、他の質問に答えられない場合、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 (program-counter) の代わりにヒープに現在の操作の状態を保持することです。 そのため、たくさんのヘルパークラスが必要になります。

例えば

理想的な世界では、次のようなコンシューマを書きたいとしましょう(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();
    }
}

同様のトリックは、他の基本的な制御フロー構造についても可能です。 理想的には、これらのヘルパークラスのライブラリを構築し、それらを使って、最終的に共同ルーチンのセマンティクスを与えるこれらの単純なインタフェースを実装することでしょう。 ここに書いたことはすべて一般化でき、大きく拡張できるはずです。