2 つの CPU があり、fork/join フレームワークが動作するように 1000 のタスクをスケジュールする場合、タスクは一度に最大 2 つ実行されますか、それとも同じ CPU でより多くのタスクが並行して実行されますか? (たとえば、1 つのタスクが I/O を待機している場合、CPU がアイドル状態になり、別のスレッドが実行される可能性があります)
5 に答える
制限を自分で含めない場合、何も適用されず、Java は可能な限り多くのスレッドをフォークします (システムの制限によっては 1000 すべてになる場合もあります)。これは理想的ではありません。いくらかの IO 時間がありそうな計算を行っているが、大量の同時処理でも IO バウンドにならない場合は、利用可能な CPU の数よりも 1 つ多くスレッドを実行することを正当化できるかもしれません。一度に 1000 個すべてを実行するのは賢明ではありません。
2 つの CPU があり、フォーク/ジョイン フレームワークが動作するように 1000 のタスクをスケジュールする場合、タスクは一度に最大 2 つ実行されますか、それとも同じ CPU でより多くのタスクが並行して実行されますか?
デュアル コア CPU を使用している場合、実際には一度に 2 つのスレッドしか実行できません。
ForkJoin のドキュメントによると:
ForkJoinPool は、指定されたターゲット並列処理レベルで構築されます。 デフォルトでは、利用可能なプロセッサーの数と同じです。プールは、一部のタスクが他のタスクへの参加を待機して停止している場合でも、内部ワーカー スレッドを動的に追加、一時停止、または再開することによって、十分なアクティブな (または使用可能な) スレッドを維持しようとします。ただし、ブロックされた IO やその他の管理されていない同期に直面した場合、そのような調整は保証されません。
したがって、おそらく2つのCPUで一度に2つ、CPUがハイパースレッドの場合は一度に4つ実行されます(よくわかりません)。既定の並列処理レベルに満足できない場合は、並列処理レベルをパラメーターとして受け取る ForkJoinPool コンストラクターを呼び出して、要求された並列処理レベルを指定できます。
CPUでハイパースレッディングが有効になっていますか? その場合、同時に 2 つ以上のプロセスを実行できます。
ハイパースレッディングは、プロセッサの特定のセクション (アーキテクチャの状態を格納するセクション) を複製することによって機能しますが、メインの実行リソースは複製しません。これにより、ハイパースレッディング プロセッサがホスト オペレーティング システムに対して 2 つの「論理」プロセッサとして表示され、オペレーティング システムが 2 つのスレッドまたはプロセスを同時にスケジュールできるようになります。
これを確認するためにテストを行いました:
import java.util.concurrent.*;
public class Test {
private static class TestAction extends RecursiveAction {
private int i;
public TestAction(int i) {
this.i = i;
}
protected void compute() {
if (i == 0) {
invokeAll(new TestAction(1), new TestAction(2), new TestAction(3),
new TestAction(4), new TestAction(5), new TestAction(6));
return;
}
System.out.println(i + " start");
try { Thread.sleep(2000); } catch (Exception e) { }
System.out.println(i + " end");
}
}
public static void main(String[] args) {
new ForkJoinPool().invoke(new TestAction(0));
}
}
リファレンスの Oracle 実装で実行した結果は次のとおりです。
1 start
6 start <- wait 2 seconds
1 end
2 start
6 end
5 start <- wait 2 seconds
2 end
3 start
5 end
4 start <- wait 2 seconds
4 end
3 end
Linux と Mac OS X の両方で同じ動作が一貫しています。
したがって、質問に対する答えは次のとおりです。はい、タスクは、parallelism パラメーターで指定された正確な数の CPU (またはデフォルトで使用可能な CPU の合計) で実行されます。CPU 時間が利用可能になり、タスクが何かを待っているだけでブロックされた場合、フレームワークは他のタスクを実行するために自動的に何もしません。
私がこれまでに見たドキュメントは、CPU が空いている場合にフレームワークが正確に何をすべきかについてかなり曖昧なので、これは実装の詳細になる可能性があります。
デフォルトでは、Fork/Join フレームワークは、コア数より 1 少ないスレッド数を維持しようとします (シングル コア マシンの場合は、1 つのスレッドが作成されます)。このコードは、クラスのmakeCommonPool
メソッドで確認できます。ForkJoinPool
これにより CPU の使用率が低いと思われる場合は、 にカスタム値を指定できますparallelism
。
しかし、最も興味深いのは、現在のスレッドが IO で CPU ブロックを占有しているときに、ForkJoinPool がより多くのスレッドを作成するようにする方法があることです。block
オブジェクトのメソッドの実装内で IO を実際にブロックするコード ブロックを実装し、そのオブジェクトをクラスのメソッドにForkJoinPool.ManagedBlocker
渡すだけです。これが完了すると、は、このメソッドを呼び出している現在のスレッドが のインスタンスであるかどうかをチェックします。そうである場合、 は、CPU を引き継ぐことができる新しいスレッドを作成することによって補償します。ManagedBlocker
managedBlock
ForkJoinPool
ForkJoinPool
ForkJoinPoolWorkerThread
ForkjoinPool
ForkJoinPool fjp = ForkJoinPool.common();
Runnable task = new Runnable(){
public void run(){
//Some cpu-intensive code
ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker(){
public boolean isReleasable(){
//return true if an IO/blocking operation is to be done.
}
public boolean block(){
//Do an IO Operation here
//return true if all blocking code has finished execution.
//return false if more blocking code is yet to execute.
}
});
//Some more CPU intensive code here
}
};
fjp.submit(task);