私はしばらくの間、このための適切な実装について困惑してきました。長時間実行されるI/O操作(ファイルのダウンロード)と長時間実行されるCPU操作(その内容の解析)を行うプログラムがあります。効率を向上させるために、1つのスレッドプールでI/Oセクションを実行し、CPUセクションを実行する別のスレッドプールにタスクを渡させる必要がありました。そうすれば、すべてのスレッドがI/Oを占有したり、CPUを占有したりすることはほとんどありません。ファイルによっては、1つの操作に他の操作よりも時間がかかる可能性があります。
スレッドプール間でハンドオフを実行するための最良の方法は何ですか? Javaを2つのExecutorServicesで使用していると仮定すると、次のようになります。
int threads = Runtime.getRuntime().availableProcessors();
ExecutorService ioService = Executors.newFixedThreadPool(threads);
ExecutorService cpuService = Executors.newFixedThreadPool(threads);
public BigFile ioTask(){
return Connection.downloadBigFile();
}
public void cpuTask(BigFile bigFile){
processBigFile(bigFile);
}
タスクをioService
で実行し、データを渡しながらcpuService
にタスクを追加する最良の方法は何ですか?
私が考えたこと:
Runnable
でcpuService
を実行するCallable
をioService
に送信します。 cpuService
の実行中にCallable
をブロックします。Runnable
でioService
を実行するRunnable
をcpuService
に送信します。 2番目のioService
の実行中にRunnable
をブロックします。Runnable
コンストラクターを持つIOTaskResult
実装を作成し、ioService
runnablesがこれらの実装をcpuService
に送信できるようにします。再利用可能な処理オブジェクトを消費可能なプロセスオブジェクトに変換します。これにより、オーバーヘッドが増加します。Runnable
を生成するメソッドをCPUタスクプロセッサに追加します。これは、私が呼び出すことができるメソッドがあり、Runnable
がメソッドをラップしてパラメーターを入力するだけなので、回避策のような「ハック」のように感じられます。これを処理する良い方法はありますか?私はいくつかの考えを聞いてみたいです。
効率を向上させるために、1つのスレッドプールでI/Oセクションを実行し、CPUセクションを実行する別のスレッドプールにタスクを渡させる必要がありました。そうすれば、すべてのスレッドがI/Oを占有したり、CPUを占有したりすることはほとんどありません。
これを実現するために実際には2つのスレッドプールは必要ありません。そのように実行すると、実行と複雑さの観点から逆効果になる可能性があります。
まず、スレッドは安価であり、数百のスレッドを実行する予定がない場合は、ダウンロードとプロセスのジョブごとに1つ使用することをためらわないでください。プロセスのI/Oバウンド部分は、スリープに多くの時間を費やすため、次のフレームが到着したときにCPUの数サイクルを取得してスリープに戻ることができるようにする必要があります。たくさんのファイルが同時にダウンロードされて後で戻ってくる場合に発生するスローダウンをハンドウェーブします。
現在の設計に基づくと、この場合、効率の定義は、最初にダウンロードされたファイルが最初に処理されることを意味しているように見えます。これは、システムのコアの数をカウントする セマフォ を使用して調整できます。
int num_cores = Runtime.getRuntime().availableProcessors();
core = new Semaphore(num_cores);
これにより、最初のnum_cores
コアを使用してコアを取得するスレッドは、コアが解放されるまで待機する必要があります。コアを使用してスレッドの数を調整する方法により、プロセス全体を単一のルーチンに削減できます。
void downloadFileAndProcess(String url) {
BigFile bigFile = Connection.downloadBigFile(url);
core.acquire(); // Hold here for a core
// TODO: Wrap in try/catch/finally for error resistance.
processBigFile(bigFile);
core.release();
}
同時ダウンロードの数は、最初のステートメントの前後で取得および解放される2番目のセマフォで調整できます。
*そうでない場合、スレッドの数をコアの数まで減らし続ける限り、スレッドの数を制限しても効率は得られません。 n実行するCPUサイクルとcコアがある場合、 n/cサイクルに相当する時間は、どのようにスライスしても関係ありません。
ioService
はavailableCores()
サイズであってはなりません。そのサイズは、コンピュータのネットワーク特性に依存し、制限がない場合さえあります。
ブロックしたり待機したりしないでください。これは時間の浪費です。 Runnable
をioService
に送信し、ファイルをダウンロードして、別のRunnable
をcpuService
に投稿し、その内容を解析します。
Nio2の非同期を使用することもできますIOスレッドをブロックしないため、cpuService
のみを使用することになります。使用するForkJoinPool
ロックフリーのキュー。