フォーク/ジョインフレームワークはスレッドプールよりも優れている点は何ですか? 質問する

フォーク/ジョインフレームワークはスレッドプールよりも優れている点は何ですか? 質問する

新しいものを使うメリットは何ですか?フォーク/ジョインフレームワーク最初に大きなタスクをN個のサブタスクに分割し、それらをキャッシュされたスレッドプール(執行者) を実行し、各タスクが完了するのを待つのでしょうか? フォーク/ジョインの抽象化を使用することで、問題がどのように簡素化されるのか、また、これまで何年も使用してきたソリューションよりもソリューションが効率的になるのか、私にはわかりません。

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

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 個のプロセッサ) がある場合、すべてのプロセッサは計算全体が完了するまでフル スピードで動作します。

何か見落としているのでしょうか? フォーク/ジョイン フレームワークを使用することで得られる付加価値は何でしょうか?

ベストアンサー1

基本的な誤解は、Fork/Joinの例がない仕事を見せて窃盗しかし、それはある種の標準的な分割統治にすぎません。

仕事の横取りは、次のようなものです。労働者 B は仕事を終えました。彼は親切な人なので、周りを見回すと、労働者 A がまだ一生懸命働いているのに気づきます。彼は歩いて行って、「やあ、手伝ってあげるよ」と尋ねます。A は答えます。「いいだろう、1000 ユニットの作業があるんだ。今のところ 345 を終えて、655 が残っている。673 から 1000 までやってもらえないか。346 から 672 は私がやるから。」B は「よし、早くパブに行けるように始めよう。」と言います。

ご存知のとおり、作業員は実際の作業を開始した後も、互いに通信する必要があります。これが例に欠けている部分です。

一方、例では「下請け業者を使う」といったことしか示されていません。

作業員 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)、「分割」フェーズと「作業」フェーズが交互に配置されます。

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

おすすめ記事