web-dev-qa-db-ja.com

I / OおよびCPUタスク用の個別のスレッドプール

私はしばらくの間、このための適切な実装について困惑してきました。長時間実行される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にタスクを追加する最良の方法は何ですか?

私が考えたこと:

  • RunnablecpuServiceを実行するCallableioServiceに送信します。 cpuServiceの実行中にCallableをブロックします。
  • RunnableioServiceを実行するRunnablecpuServiceに送信します。 2番目のioServiceの実行中にRunnableをブロックします。
  • Runnableコンストラクターを持つIOTaskResult実装を作成し、ioService runnablesがこれらの実装をcpuServiceに送信できるようにします。再利用可能な処理オブジェクトを消費可能なプロセスオブジェクトに変換します。これにより、オーバーヘッドが増加します。
  • Runnableを生成するメソッドをCPUタスクプロセッサに追加します。これは、私が呼び出すことができるメソッドがあり、Runnableがメソッドをラップしてパラメーターを入力するだけなので、回避策のような「ハック」のように感じられます。

これを処理する良い方法はありますか?私はいくつかの考えを聞いてみたいです。

7
ndm13

効率を向上させるために、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サイクルに相当する時間は、どのようにスライスしても関係ありません。

8
Blrfl

ioServiceavailableCores()サイズであってはなりません。そのサイズは、コンピュータのネットワーク特性に依存し、制限がない場合さえあります。

ブロックしたり待機したりしないでください。これは時間の浪費です。 RunnableioServiceに送信し、ファイルをダウンロードして、別のRunnablecpuServiceに投稿し、その内容を解析します。

Nio2の非同期を使用することもできますIOスレッドをブロックしないため、cpuServiceのみを使用することになります。使用するForkJoinPoolロックフリーのキュー。

2
Miha_x64