fork / joinフレームワークはスレッドプールよりも優れていますか?


134

大きなタスクを最初にN個のサブタスクに分割し、それらを(Executorsから)キャッシュされたスレッドプールに送信し、各タスクが完了するのを待つだけで、新しいfork / joinフレームワークを使用する利点は何ですか?fork / join抽象化を使用することで問題が単純化される、またはソリューションが何年にもわたって持ってきたものよりも効率的になる方法を理解できません。

たとえば、チュートリアルの例の並列化されたぼかしアルゴリズムは、次のように実装できます。

public class Blur implements Runnable {
    private int[] mSource;
    private int mStart;
    private int mLength;
    private int[] mDestination;

    private int mBlurWidth = 15; // Processing window size, should be odd.

    public ForkBlur(int[] src, int start, int length, int[] dst) {
        mSource = src;
        mStart = start;
        mLength = length;
        mDestination = dst;
    }

    public void run() {
        computeDirectly();
    }

    protected void computeDirectly() {
        // As in the example, omitted for brevity
    }
}

最初に分割し、タスクをスレッドプールに送信します。

// source image pixels are in src
// destination image pixels are in dst
// threadPool is a (cached) thread pool

int maxSize = 100000; // analogous to F-J's "sThreshold"
List<Future> futures = new ArrayList<Future>();

// Send stuff to thread pool:
for (int i = 0; i < src.length; i+= maxSize) {
    int size = Math.min(maxSize, src.length - i);
    ForkBlur task = new ForkBlur(src, i, size, dst);
    Future f = threadPool.submit(task);
    futures.add(f);
}

// Wait for all sent tasks to complete:
for (Future future : futures) {
    future.get();
}

// Done!

タスクはスレッドプールのキューに移動し、そこからワーカースレッドが使用可能になるとタスクが実行されます。分割が十分に細かく(特に最後のタスクを待つ必要がないようにするため)、スレッドプールに十分な(少なくともN個のプロセッサ)スレッドがある限り、すべてのプロセッサは計算全体が完了するまでフルスピードで動作します。

何か不足していますか?fork / joinフレームワークを使用することの付加価値は何ですか?

回答:


136

私は基本的な誤解がフォーク/参加例がないということ、だと思うしないで作業を示して盗むが、標準的な分割統治の唯一のいくつかの種類を。

仕事の盗難は次のようになります。労働者Bが仕事を終えました。彼は優しい人なので、周りを見回すと、ワーカーAがまだ非常に懸命に働いているのがわかります。彼は散歩し、尋ねます:「おいおい、私はあなたに手を差し伸べることができました。」返信。「かしこまりました。このタスクには1000ユニットあります。これまでに345を終えて655を残しました。673から1000まで作業していただけますか、346から672を実行します。」Bさんは「OK、始めましょう。早くパブに行けるようにします」と言います。

あなたが見る-労働者は彼らが実際の仕事を始めたときでさえ、お互いの間でコミュニケーションしなければなりません。これは、例に欠けている部分です。

一方、例では「下請け業者を使用」のようなものだけを示しています。

労働者A:「ダン、私には1000単位の仕事があります。私には多すぎます。自分で500を行い、他の誰かに500を下請けします。」これは、大きなタスクがそれぞれ10ユニットの小さなパケットに分割されるまで続きます。これらは、利用可能なワーカーによって実行されます。しかし、1つのパケットが一種の毒薬であり、他のパケットよりもかなり時間がかかる場合-運が悪ければ、分割フェーズは終了です。

Fork / Joinとタスクを前もって分割する場合の唯一の違いは、次のとおりです。前に分割すると、最初から作業キューがいっぱいになります。例:1000ユニット、しきい値は10なので、キューには100エントリがあります。これらのパケットは、スレッドプールメンバーに配布されます。

Fork / Joinはより複雑であり、キュー内のパケット数を少なく維持しようとします。

  • ステップ1:(1 ... 1000)を含む1つのパケットをキューに入れる
  • ステップ2:1つのワーカーがパケット(1 ... 1000)をポップし、2つのパケット((1 ... 500)と(501 ... 1000)に置き換えます。
  • ステップ3:1つのワーカーがパケット(500 ... 1000)をポップし、(500 ... 750)と(751 ... 1000)をプッシュします。
  • ステップn:スタックには次のパケットが含まれています:(1..500)、(500 ... 750)、(750 ... 875)...(991..1000)
  • ステップn + 1:パケット(991..1000)がポップされ、実行されます
  • ステップn + 2:パケット(981..990)がポップされて実行されます
  • ステップn + 3:パケット(961..980)がポップされ、(961 ... 970)と(971..980)に分割されます。....

次のように表示されます。Fork/ Joinでは、キューが小さくなり(例では6)、「分割」フェーズと「作業」フェーズがインターリーブされます。

複数のワーカーがポップとプッシュを同時に行っている場合、相互作用はもちろんそれほど明確ではありません。


これこそが答えだと思います。実際のFork / Joinの例があり、そのワークスチール機能も示しているのでしょうか。基本的な例では、ワークロードの量はユニットのサイズ(たとえば、アレイの長さ)からかなり完全に予測できるため、事前分割は簡単です。スチールは、ユニットのサイズからユニットあたりのワークロードの量を十分に予測できない問題に確かに影響を及ぼします。
Joonas Pulakka、2011年

AH答えが正しい場合、その方法は説明されていません。Oracleが提供する例では、作業の盗難は発生しません。ここで説明している例のように、フォークとジョインはどのように機能しますか?フォークおよびジョインスティールを、説明どおりに動作させるJavaコードをいくつか示していただけますか?感謝
Marc

@Marc:申し訳ありませんが、使用できる例はありません。
AH

6
Oracleの例であるIMOの問題は、(AHで説明されているように)作業の盗用を示していないことではなく、同様に(Joonasが行ったように)単純なThreadPoolのアルゴリズムを簡単にコーディングできることです。FJは、作業を十分に独立したタスクに事前に分割できないが、再帰的にそれらの間で独立したタスクに分割できる場合に最も役立ちます。例については私の回答を参照してください
アシリー

2
:仕事スチールが便利になるかもしれない場所のいくつかの例h-online.com/developer/features/...
ボレー

27

n個のビジースレッドがすべて100%で独立して動作している場合、それはFork-Join(FJ)プールのn個のスレッドよりも優れています。しかし、それはそのようにはうまくいきません。

問題をn個の等しい部分に正確に分割できない場合があります。あなたがそうしたとしても、スレッドのスケジューリングは公平であるとは少しかけ離れています。最も遅いスレッドを待つことになります。複数のタスクがある場合、それらはそれぞれnウェイ未満の並列処理で実行できますが(通常はより効率的)、他のタスクが終了するとnウェイまで上がります。

それでは、問題をFJサイズに細かく切り分けて、スレッドプールを機能させてみませんか。FJの一般的な使用法は、問題を小さな断片に分割します。これらをランダムな順序で行うには、ハードウェアレベルで多くの調整が必要です。オーバーヘッドはキラーになります。FJでは、タスクは、スレッドが後入れ先出し順(LIFO /スタック)で読み取るキューに入れられ、(コア作業では、一般に)作業スチールは先入れ先出し(FIFO /「キュー」)で行われます。その結果、長い配列の処理は、小さなチャンクに分割されていても、ほとんど順番に実行できます。(問題を1つのビッグバンで小さな均等なサイズのチャンクに分割することは簡単ではない場合もあります。バランスをとらずに何らかの形式の階層を処理するとします。)

結論:FJでは、不均一な状況でハードウェアスレッドをより効率的に使用できます。これは、複数のスレッドがある場合は常にそうです。


しかし、なぜFJが最も遅いスレッドも待たないのですか?事前に決定された数のサブタスクがあり、もちろんそれらのいくつかは常に最後に完了するものです。maxSize私の例でパラメータを調整すると、FJの例の「バイナリ分割」とほぼ同様のサブタスク除算が生成されます(compute()何かを計算するか、サブタスクをに送信するメソッド内で行われますinvokeAll())。
Joonas Pulakka、2011年

彼らははるかに小さいので-私は私の答えに追加します。
トム・ホーティン-タックライン

サブタスクの数が実際に並行して処理できるものよりも桁違いに大きい場合(つまり、最後のタスクを待つ必要がないようにするのは理にかなっています)、調整の問題を確認できます。FJの例は、分割がそのようにきめ細かくなっている場合、誤解を招く可能性があります。1000x1000の画像の場合、16500の実際のサブタスクを生成し、それぞれが62500要素を処理する100000のしきい値を使用します。10000x10000の画像の場合、1024のサブタスクが存在しますが、これはすでに何かです。
Joonas Pulakka、2011年

19

スレッドプールとフォーク/ジョインの最終的な目標は同じです。どちらも、利用可能なCPUパワーを最大限に活用して、最大のスループットを実現したいと考えています。最大スループットとは、できるだけ多くのタスクを長期間で完了する必要があることを意味します。それには何が必要ですか?(以下では、計算タスクが不足していないと仮定します:CPU使用率が100%の場合は常に十分です。さらに、ハイパースレッディングの場合は、コアまたは仮想コアに「CPU」を同等に使用します)。

  1. 実行するCPUの数と同じ数のスレッドを実行する必要があります。実行するスレッドが少なくなると、コアが未使用のままになるためです。
  2. 使用可能なCPUの数と同じ数のスレッドが実行されている必要があります。実行するスレッドが増えると、CPUを別のスレッドに割り当てるスケジューラに追加の負荷がかかり、CPU時間が計算タスクではなくスケジューラに送られるようになります。

したがって、最大のスループットを得るには、CPUとまったく同じ数のスレッドが必要であることがわかりました。Oracleのぼかしの例では、使用可能なCPUの数と同じ数のスレッドを持つ固定サイズのスレッドプールを使用することも、スレッドプールを使用することもできます。違いはありません、あなたは正しいです!

では、いつスレッドプールで問題が発生するのでしょうか。これは、スレッドが別のタスクの完了を待機しているため、スレッドがブロックした場合です。次の例を想定します。

class AbcAlgorithm implements Runnable {
    public void run() {
        Future<StepAResult> aFuture = threadPool.submit(new ATask());
        StepBResult bResult = stepB();
        StepAResult aResult = aFuture.get();
        stepC(aResult, bResult);
    }
}

ここに表示されるのは、3つのステップA、B、Cで構成されるアルゴリズムです。AとBは互いに独立して実行できますが、ステップCにはステップAとBの結果が必要です。スレッドプールとタスクbを直接実行します。その後、スレッドはタスクAが完了するまで待機し、ステップCに進みます。AとBが同時に完了している場合は、すべて正常です。しかし、AがBよりも時間がかかるとどうなりますか?これは、タスクAの性質がそれを指示するためである可能性がありますが、タスクAのスレッドが最初に使用可能でなく、タスクAが待機する必要があるためである場合もあります。(CPUが1つしかなく、スレッドプールに1つのスレッドしかない場合、これによりデッドロックが発生しますが、今のところそれは問題です。)ポイントは、タスクBを実行したスレッドがスレッド全体をブロックします。CPUと同じ数のスレッドがあり、1つのスレッドがブロックされているため、1つのCPUがアイドル状態になります。

フォーク/ジョインはこの問題を解決します。フォーク/ジョインフレームワークでは、次のように同じアルゴリズムを記述します。

class AbcAlgorithm implements Runnable {
    public void run() {
        ATask aTask = new ATask());
        aTask.fork();
        StepBResult bResult = stepB();
        StepAResult aResult = aTask.join();
        stepC(aResult, bResult);
    }
}

同じに見えますか?しかし手掛かりはそれaTask.join がブロックしないことです。代わりに、ここでワークスチールが行われます。スレッドは、過去に分岐された他のタスクを探し、それらを続行します。最初に、それ自体が分岐したタスクが処理を開始したかどうかをチェックします。そのため、Aが別のスレッドによってまだ開始されていない場合は、次にAを実行します。それ以外の場合は、他のスレッドのキューをチェックして作業を盗みます。別のスレッドのこの他のタスクが完了すると、Aが今完了したかどうかをチェックします。上記のアルゴリズムの場合、を呼び出すことができますstepC。それ以外の場合は、盗む別のタスクを探します。したがって、fork / joinプールは、ブロッキングアクションが発生した場合でも、100%のCPU使用率を達成できます。

ただし、トラップがあります。ワークスティーリングはs のjoin呼び出しに対してのみ可能ですForkJoinTask。別のスレッドを待機したり、I / Oアクションを待機したりするなど、外部のブロックアクションに対しては実行できません。では、I / Oが完了するのを待つのは一般的なタスクでしょうか。この場合、追加のスレッドをFork / Joinプールに追加できれば、ブロックアクションが完了するとすぐに停止し、2番目に最適な方法になります。そして、s ForkJoinPoolを使用している場合、実際にそれを行うことができManagedBlockerます。

フィボナッチ

RecursiveTaskJavaDocには、フォーク/ジョインを使用してフィボナッチ数を計算する例があります。従来の再帰的ソリューションについては、以下を参照してください。

public static int fib(int n) {
    if (n <= 1) {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

JavaDocsで説明されているように、このアルゴリズムはO(2 ^ n)の複雑さを持ちながらより単純な方法が可能であるため、これはフィボナッチ数を計算するかなりダンプな方法です。ただし、このアルゴリズムは非常にシンプルで理解しやすいので、そのまま使用します。これをFork / Joinでスピードアップしたいとします。素朴な実装は次のようになります。

class Fibonacci extends RecursiveTask<Long> {
    private final long n;

    Fibonacci(long n) {
        this.n = n;
    }

    public Long compute() {
        if (n <= 1) {
            return n;
        }
        Fibonacci f1 = new Fibonacci(n - 1);
        f1.fork();
        Fibonacci f2 = new Fibonacci(n - 2);
        return f2.compute() + f1.join();
   }
}

このタスクが分割されるステップは短すぎるため、これは恐ろしく実行されますが、フレームワークが一般的に非常にうまく機能していることがわかります。結果。したがって、半分は別のスレッドで行われます。デッドロックを起こさずにスレッドプールで同じことを楽しんでください(可能ですが、それほど単純ではありません)。

完全を期すために:この再帰的アプローチを使用してフィボナッチ数列を実際に計算したい場合は、ここに最適化バージョンがあります。

class FibonacciBigSubtasks extends RecursiveTask<Long> {
    private final long n;

    FibonacciBigSubtasks(long n) {
        this.n = n;
    }

    public Long compute() {
        return fib(n);
    }

    private long fib(long n) {
        if (n <= 1) {
            return 1;
        }
        if (n > 10 && getSurplusQueuedTaskCount() < 2) {
            final FibonacciBigSubtasks f1 = new FibonacciBigSubtasks(n - 1);
            final FibonacciBigSubtasks f2 = new FibonacciBigSubtasks(n - 2);
            f1.fork();
            return f2.compute() + f1.join();
        } else {
            return fib(n - 1) + fib(n - 2);
        }
    }
}

これは、サブタスクn > 10 && getSurplusQueuedTaskCount() < 2がtrueの場合にのみ分割されるため、サブタスクをはるかに小さく保ちます。つまり、実行するメソッド呼び出しが100を大幅に超えることになります(n > 10)、待機しているmanタスクはほとんどありません(getSurplusQueuedTaskCount() < 2)。

私のコンピューター(4コア(ハイパースレッディングを数えると8)、Intel(R)Core(TM)i7-2720QM CPU @ 2.20GHz)では、 fib(50)、クラシックアプローチでは64秒、フォーク/ジョインアプローチではわずか18秒かかります。理論的に可能な限りではありませんが、かなり顕著な利得です。

概要

  • はい、あなたの例では、Fork / Joinは従来のスレッドプールに勝るものはありません。
  • フォーク/ジョインは、ブロッキングが関係している場合にパフォーマンスを大幅に向上させることができます
  • フォーク/ジョインはいくつかのデッドロック問題を回避します

17

フォーク/ジョインは、ワークスチールを実装するため、スレッドプールとは異なります。フォーク/ジョインから

他のExecutorServiceと同様に、fork / joinフレームワークはスレッドプール内のワーカースレッドにタスクを分散します。fork / joinフレームワークは、ワークスチールアルゴリズムを使用しているため、独特です。やることが不足しているワーカースレッドは、まだビジーな他のスレッドからタスクを盗む可能性があります。

2つのスレッドと4つのタスクa、b、c、dがあり、それぞれ1、1、5、6秒かかるとします。最初は、aとbがスレッド1に、cとdがスレッド2に割り当てられています。スレッドプールでは、これには11秒かかります。fork / joinを使用すると、スレッド1が終了し、スレッド2から作業を盗むことができるため、タスクdはスレッド1によって実行されます。スレッド1はa、b、dを実行し、スレッド2はcだけを実行します。合計時間:11秒ではなく8秒。

編集:Joonasが指摘するように、タスクは必ずしもスレッドに事前に割り当てられるとは限りません。fork / joinのアイデアは、スレッドがタスクを複数のサブピースに分割することを選択できることです。上記を言い換えると:

2つのタスク(ab)と(cd)があり、それぞれ2秒と11秒かかります。スレッド1はabの実行を開始し、それを2つのサブタスクaとbに分割します。同様にスレッド2では、2つのサブタスクcおよびdに分割されます。スレッド1がaおよびbを完了すると、スレッド2からdを盗むことができます。


5
スレッドプールは通常、ThreadPoolExecutorインスタンスです。そのような場合、タスクはキュー(実際にはBlockingQueue)に移動し、ワーカースレッドは前のタスクが完了するとすぐにキューからタスクを取得します。私が理解している限り、タスクは特定のスレッドに事前に割り当てられていません。各スレッドには、一度に(最大で)1つのタスクがあります。
Joonas Pulakka、2011年

4
私の知る限り、順番にいくつかのスレッドを制御する1つの ThreadPoolExecutor に対して1 つのキューがあります。つまり、エグゼキュータにタスクまたは実行可能オブジェクト(スレッドではありません!)を割り当てると、タスクも特定のスレッドに事前割り当てされません。FJもそれを正確に行う方法です。これまでのところ、FJを使用するメリットはありません。
AH

1
@AHはい。ただし、fork / joinを使用すると、現在のタスクを分割できます。タスクを実行しているスレッドは、タスクを2つの異なるタスクに分割できます。したがって、ThreadPoolExecutorを使用すると、タスクの固定リストが得られます。fork / joinを使用すると、実行中のタスクは自身のタスクを2つに分割でき、作業が終了したときに他のスレッドがそれを取得できます。または、最初に終了した場合。
Matthew Farwell、2011年

1
@Matthew Farwell:FJの例では、各タスク内で、タスクをcompute()計算するか、2つのサブタスクに分割します。どのオプションを選択するは、タスクのサイズ(if (mLength < sThreshold)...)にのみ依存するため、固定数のタスクを作成するための優れた方法です。1000x1000の画像の場合、実際に何かを計算するサブタスクがちょうど16個あります。さらに、サブタスクを生成して呼び出すだけで、それ自体は何も計算しない15(= 16-1)の「中間」タスクがあります。
Joonas Pulakka、2011年

2
@Matthew Farwell:私はFJのすべてを理解していない可能性がありますが、サブタスクがそのcomputeDirectly()メソッドを実行することを決定した場合、これ以上何かを盗む方法はありません。全体の分割は、少なくともこの例ではアプリオリに行われます。
Joonas Pulakka、2011年

14

上記の誰もが正しいことは、仕事を盗むことによって得られる利点ですが、これがなぜかを拡大します。

主な利点は、ワーカースレッド間の効率的な調整です。作業は分割して再構成する必要があり、調整が必要です。上記のAHの回答からわかるように、各スレッドには独自の作業リストがあります。このリストの重要な特性は、リストがソートされていることです(上部に大きなタスク、下部に小さなタスク)。各スレッドは、そのリストの下部にあるタスクを実行し、他のスレッドリストの上部からタスクを盗みます。

これの結果は:

  • タスクリストの先頭と末尾を個別に同期できるため、リストの競合が減少します。
  • 作業の重要なサブツリーは分割され、同じスレッドによって再構築されるため、これらのサブツリーに対してスレッド間の調整は必要ありません。
  • スレッドが仕事を盗むとき、それはそれがそれ自身のリストに細分される大きな部分をとります
  • 作業鋼は、スレッドがプロセスの終わりまでほぼ完全に利用されることを意味します。

スレッドプールを使用する他のほとんどの分割統治スキームでは、より多くのスレッド間通信と調整が必要です。


13

この例では、フォークが不要であり、ワークロードがワーカースレッド間で均等に分割されるため、Fork / Joinは値を追加しません。フォーク/ジョインはオーバーヘッドを追加するだけです。

これはこの件に関する素晴らしい記事です。見積もり:

全体として、ワークロードがワーカースレッド間で均等に分割される場合は、ThreadPoolExecutorが推奨されます。これを保証するには、入力データがどのように見えるかを正確に知る必要があります。対照的に、ForkJoinPoolは入力データに関係なく優れたパフォーマンスを提供するため、非常に堅牢なソリューションです。


8

もう1つの重要な違いは、FJでは複数の複雑な「結合」フェーズを実行できることです。http://faculty.ycp.edu/~dhovemey/spring2011/cs365/lecture/lecture18.htmlのマージソートを検討してください。この作業を事前に分割するには、オーケストレーションが多すぎます。たとえば、次のことを行う必要があります。

  • 第1四半期を並べ替える
  • 第2四半期を並べ替える
  • 最初の2つの四半期をマージする
  • 第3四半期を並べ替える
  • 第4四半期を並べ替える
  • 過去2四半期を統合する
  • 2つの半分をマージ

それらに関係するマージの前にソートを行う必要があることをどのように指定しますか?

私は、アイテムのリストのそれぞれに対して特定のことを行うための最善の方法を見てきました。私はリストを事前に分割して、標準のThreadPoolを使用すると思います。FJは、作業を十分に独立したタスクに事前に分割できないが、相互に独立したタスクに再帰的に分割できる場合に最も便利です(たとえば、半分をソートすると独立しているが、ソートされた2つの半分をマージしてソート済み全体にできない場合)。


6

F / Jには、コストのかかるマージ操作がある場合にも明確な利点があります。これはツリー構造に分割されるため、線形スレッド分割によるnマージではなく、log2(n)マージのみを実行します。(これは、スレッドと同じくらい多くのプロセッサがあるという理論上の仮定を行いますが、それでも利点です)宿題を割り当てるには、各インデックスの値を合計して数千の2D配列(すべて同じ次元)をマージする必要がありました。フォークジョインとPプロセッサでは、Pが無限大に近づくにつれて、時間がlog2(n)に近づきます。

1 2 3 .. 7 3 1 .... 8 5 4
4 5 6 + 2 4 3 => 6 9 9
7 8 9 .. 1 1 0 .... 8 9 9


3

クローラーのようなアプリケーションでのForkJoinのパフォーマンスには驚かれることでしょう。これがあなたが学ぶ最高のチュートリアルです。

フォーク/ジョインのロジックは非常に単純です。(1)各大きなタスクを小さなタスクに分離(フォーク)します。(2)各タスクを個別のスレッドで処理します(必要に応じて、タスクをさらに小さなタスクに分けます)。(3)結果を結合します。


3

問題が他のスレッドが完了するのを待たなければならないようなものである場合(配列のソートまたは配列の合計の場合のように)、制限のためにExecutor(Executors.newFixedThreadPool(2))がチョークするため、フォーク結合を使用する必要がありますスレッドの数。この場合、forkjoinプールはより多くのスレッドを作成し、ブロックされたスレッドが同じ並列性を維持できるように隠蔽します。

ソース: http : //www.oracle.com/technetwork/articles/java/fork-join-422606.html

Callableは新しいサブタスクをエグゼキューターにサブミットし、その結果を同期または非同期で自由に待機できるため、分割統治アルゴリズムを実装するためのエグゼキューターの問題はサブタスクの作成とは関係ありません。問題は並列処理の問題です。Callableが別のCallableの結果を待つと、Callableは待機状態になり、実行のためにキューに入れられた別のCallableを処理する機会を無駄にします。

Doug Leaの努力によりJava SE 7のjava.util.concurrentパッケージに追加されたfork / joinフレームワークがそのギャップを埋めます

ソース: https : //docs.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinPool.html

プールは、一部のタスクが他のタスクへの参加を待機して停止している場合でも、内部ワーカースレッドを動的に追加、一時停止、または再開することにより、十分なアクティブ(または使用可能な)スレッドを維持しようとします。ただし、そのような調整は、ブロックされたIOまたはその他の管理されていない同期に直面しても保証されません

public int getPoolSize()開始したがまだ終了していないワーカースレッドの数を返します。このメソッドによって返される結果は、並列処理を維持するためにスレッドが作成され、他が協調的にブロックされている場合、getParallelism()とは異なる場合があります。


2

長い回答を読む時間があまりない人のために、短い回答を追加したいと思います。比較は、Applied Akka Patternsの本から引用しています。

fork-join-executorとthread-pool-executorのどちらを使用するかに関する決定は、主にそのディスパッチャーの操作がブロックされるかどうかに基づいています。fork-join-executorはアクティブなスレッドの最大数を提供しますが、thread-pool-executorは固定数のスレッドを提供します。スレッドがブロックされている場合、fork-join-executorはスレッドを作成しますが、thread-pool-executorは作成しません。ブロッキングオペレーションの場合、スレッドカウントの急増を防ぐため、通常はスレッドプールエグゼキューターを使用する方が適切です。fork-join-executorでは、より「反応的な」操作のほうが優れています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.