Javaには、スレッドビルディングブロックに似た並列タスクフレームワークがあります。これは、Fork-Joinフレームワークと呼ばれます。現在のJavaSE6で使用でき、今後のJavaSE7に含まれる予定です。
javadocクラスのドキュメントに加えて、フレームワークの使用を開始するために利用できるリソースがあります。jsr166ページから、「これらのクラスの追加のドキュメント、メモ、アドバイス、例などを含むwikiもあります」と記載されています。
行列の乗算などのフォーク結合の例は、開始するのに適した場所です。
インテルの2009年のスレッド化の課題のいくつかを解決するために、フォーク結合フレームワークを使用しました。フレームワークは軽量でオーバーヘッドが少ないです。私のものはKight'sTour問題の唯一のJavaエントリであり、競合他社の他のエントリを上回りました。Javaのソースと記述は、チャレンジサイトからダウンロードできます。
編集:
クラスまたはコードの一部がプールにプッシュされた後、どのように見えるかわかりません[...]
RecursiveTaskなどのForKJoinTaskサブクラスの1つをサブクラス化することにより、独自のタスクを作成できます。フィボナッチ数列を並列に計算する方法は次のとおりです。(javadocsから取得-コメントは私のものです。)RecursiveTask
// declare a new task, that itself spawns subtasks.
// The task returns an Integer result.
class Fibonacci extends RecursiveTask<Integer> {
final int n; // the n'th number in the fibonacci sequence to compute
Fibonnaci(int n) { this.n = n; } // constructor
Integer compute() { // this method is the main work of the task
if (n <= 1) // 1 or 0, base case to end recursion
return n;
Fibonacci f1 = new Fibonacci(n - 1); // create a new task to compute n-1
f1.fork(); // schedule to run asynchronously
Fibonacci f2 = new Fibonacci(n - 2); // create a new task to compute n-2
return f2.invoke() + f1.join(); // wait for both tasks to compute.
// f2 is run as part of this task, f1 runs asynchronously.
// (you could create two separate tasks and wait for them both, but running
// f2 as part of this task is a little more efficient.
}
}
次に、このタスクを実行して結果を取得します
// default parallelism is number of cores
ForkJoinPool pool = new ForkJoinPool();
Fibonacci f = new Fibonacci(100);
int result = pool.invoke(f);
これは、物事を単純にするための簡単な例です。実際には、タスクによって実行される作業はタスクフレームワークのオーバーヘッドと比較して取るに足らないものであるため、パフォーマンスはそれほど良くありません。経験則として、タスクはいくつかの重要な計算を実行する必要があります-フレームワークのオーバーヘッドを重要ではないものにするのに十分ですが、1つの大きなタスクを実行する問題の最後に1つのコアになってしまうほどではありません。大きなタスクを小さなタスクに分割することで、他のコアがアイドル状態のときに1つのコアが多くの作業を残さないようにします。小さなタスクを使用すると、より多くのコアがビジー状態になりますが、タスクが実際に機能しないほど小さくはなりません。
[...]または、すべてのコピーを作成する必要があるときにコードがどのように奇妙に見えるか、およびすべてのどれだけがプールにプッシュされるか。
タスク自体のみがプールにプッシュされます。理想的には、何もコピーしたくないです。プログラムの速度を低下させる干渉やロックの必要性を回避するために、タスクは理想的には独立したデータで動作する必要があります。読み取り専用データはすべてのタスク間で共有でき、コピーする必要はありません。スレッドが協力して大規模なデータ構造を構築する必要がある場合は、それらを個別に構築し、最後にそれらを組み合わせるのが最善です。結合は個別のタスクとして実行することも、各タスクでパズルのピースをソリューション全体に追加することもできます。多くの場合、これには何らかの形式のロックが必要ですが、タスクの作業がソリューションの更新作業よりもはるかに大きい場合は、パフォーマンスの大きな問題にはなりません。私の騎士」
タスクと並行性の操作は、通常のシングルスレッドプログラミングからのパラダイムシフトです。多くの場合、特定の問題を解決するために可能な設計はいくつかありますが、スレッド化されたソリューションに適しているのはそのうちのいくつかだけです。マルチスレッドの方法でおなじみの問題を再キャストする方法の感触をつかむには、数回の試行が必要になる場合があります。学ぶための最良の方法は、例を見て、それを自分で試すことです。常にプロファイルを作成し、スレッド数を変更した場合の影響を測定します。プールコンストラクターのプールで使用するスレッド(コア)の数を明示的に設定できます。タスクが線形に分割されると、スレッドの数が増えるにつれてほぼ線形のスピードアップが期待できます。