web-dev-qa-db-ja.com

複数のスレッドが使用可能な場合、複数のスレッドを強制的に使用する

私はJavaプログラムの性質上、多くのCPUを使用するプログラムを書いています。しかし、その多くは並行して実行でき、プログラムをマルチスレッドにしました。実行すると、必要になるまで1つのCPUのみを使用し、別のCPUを使用するようです-Javaで異なるスレッドを異なるコアで実行するためにできることはありますか/ CPU?

66
Nosrama

私がそれを実行すると、別のCPUを使用するよりも必要になるまで1つのCPUのみを使用するようです-Javaで異なるコア/ CPUで異なるスレッドを実行するためにできることはありますか? ?

質問のこの部分は、アプリケーションをマルチスレッド対応にするという問題に既に対処していることを意味すると解釈します。それにもかかわらず、すぐに複数のコアの使用を開始するわけではありません。

「強制する方法はありますか...」に対する答えは、直接ではありません。 JVMやホストOSは、使用する「ネイティブ」スレッドの数と、それらのスレッドを物理プロセッサにマップする方法を決定します。チューニングにはいくつかのオプションがあります。例えば、私は このページ を見つけましたJava Solarisでのスレッド化の調整方法について話します。そして このページ は他のことができることについて話しますマルチスレッドアプリケーションの速度を低下させます。

29
Stephen C

Javaでマルチスレッドを実行するには、2つの基本的な方法があります。これらのメソッドを使用して作成する各論理タスクは、必要に応じて利用可能なときに新しいコアで実行する必要があります。

方法1:RunnableまたはThreadオブジェクト(コンストラクターでRunnableを取得できる)を定義し、Thread.start()メソッドで実行を開始します。 OSが提供するすべてのコア(一般的に負荷の少ないコア)で実行されます。

チュートリアル: スレッドの定義と開始

方法2:Runnable(値を返さない場合)またはCallable(そうする場合)インターフェイスを実装するオブジェクトを定義します。これには処理コードが含まれます。これらをタスクとしてJava.util.concurrentパッケージからExecutorServiceに渡します。 Java.util.concurrent.Executorsクラスには、標準の便利な種類のExecutorServicesを作成するための多数のメソッドがあります。 リンク Executorsチュートリアルへ。

個人的な経験から、エグゼキューターの固定およびキャッシュされたスレッドプールは非常に優れていますが、スレッド数を微調整する必要があります。 Runtime.getRuntime()。availableProcessors()は、実行時に使用可能なコアをカウントするために使用できます。アプリケーションの終了時にスレッドプールをシャットダウンする必要があります。シャットダウンしないと、ThreadPoolスレッドの実行が継続されるため、アプリケーションは終了しません。

優れたマルチコアパフォーマンスを得るのは難しい場合があり、落とし穴がたくさんあります:

  • ディスクI/Oを並行して実行すると、LOTが遅くなります。一度に1つのスレッドのみがディスクの読み取り/書き込みを行う必要があります。
  • オブジェクトの同期は、マルチスレッド操作の安全性を提供しますが、作業を遅くします。
  • タスクが非常に簡単な場合(小さな作業ビット、高速実行)、ExecutorServiceでタスクを管理するオーバーヘッドは、複数のコアから得られるよりも多くかかります。
  • 新しいThreadオブジェクトの作成は遅いです。 ExecutorServicesは、可能であれば既存のスレッドの再利用を試みます。
  • 複数のスレッドが何かを処理すると、あらゆる種類のクレイジーなことが起こります。システムをシンプルに保ち、タスクを論理的に区別し、相互作用しないようにしてください。

もう1つの問題:作業の制御が難しい!タスクを作成して送信する1つのマネージャースレッドと、(ExecutorServiceを使用して)ワークキューを持つ2つの作業スレッドを用意することをお勧めします。

ここで重要な点に触れているだけです。マルチスレッドプログラミングは、多くの専門家によって最も難しいプログラミングの課題の1つと考えられています。それは直感的ではなく、複雑であり、抽象化はしばしば弱いです。


編集-ExecutorServiceを使用した例:

public class TaskThreader {
    class DoStuff implements Callable {
       Object in;
       public Object call(){
         in = doStep1(in);
         in = doStep2(in);
         in = doStep3(in); 
         return in;
       }
       public DoStuff(Object input){
          in = input;
       }
    }

    public abstract Object doStep1(Object input);    
    public abstract Object doStep2(Object input);    
    public abstract Object doStep3(Object input);    

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        ArrayList<Callable> tasks = new ArrayList<Callable>();
        for(Object input : inputs){
           tasks.add(new DoStuff(input));
        }
        List<Future> results = exec.invokeAll(tasks);
        exec.shutdown();
        for(Future f : results) {
           write(f.get());
        }
    }
}
56
BobMcGee

まず、プログラムが複数のコアでfasterを実行することを自分で証明する必要があります。多くのオペレーティングシステムは、同じコア上でプログラムスレッドを実行するように努力します可能な限り

同じコアで実行すると、多くの利点があります。 CPUキャッシュはホットです。つまり、そのプログラムのデータがCPUにロードされます。ロック/モニター/同期オブジェクトはCPUキャッシュにあります。つまり、他のCPUはバスを介してキャッシュ同期操作を行う必要がありません(高価です!)。

プログラムを常に同じCPUで非常に簡単に実行できる1つのことは、ロックと共有メモリの過剰使用です。スレッドは互いに対話しないでください。スレッドが同じメモリ内で同じオブジェクトを使用する頻度が少ないほど、異なるCPUで実行される頻度が高くなります。同じメモリを使用する頻度が高いほど、他のスレッドの待機をブロックしなければならない頻度が高くなります。

OSは、別のスレッドの1つのスレッドブロックを検出するたびに、可能な限り同じCPUでそのスレッドを実行します。 CPU間バスを介して移動するメモリの量を減らします。それが私があなたのプログラムで見るものを引き起こしていると私が推測することです。

18
Zan Lynx

まず、 "Concurrency in Practice" by Brian Goetz を読むことをお勧めします。

alt text

これは、並行Javaプログラミングを説明している最高の本です。

並行性は「習得が容易で、習得が困難」です。私はそれを試みる前に主題について十分に読むことをお勧めします。マルチスレッドプログラムが99.9%の時間で正しく動作し、0.1%で失敗するのは非常に簡単です。ただし、開始するためのヒントを次に示します。

プログラムに複数のコアを使用させるには、2つの一般的な方法があります。

  1. 複数のプロセスを使用してプログラムを実行します。例としては、Pre-Fork MPMでコンパイルされたApacheがあり、リクエストは子プロセスに割り当てられます。マルチプロセスプログラムでは、メモリはデフォルトでは共有されません。ただし、プロセス全体で共有メモリのセクションをマップできます。 Apacheは「スコアボード」でこれを行います。
  2. プログラムをマルチスレッド化します。マルチスレッドプログラムでは、すべてのヒープメモリはデフォルトで共有されます。各スレッドにはまだ独自のスタックがありますが、ヒープのどの部分にもアクセスできます。通常、ほとんどのJavaプログラムはマルチスレッドであり、マルチプロセスではありません。

最低レベルでは、 スレッドの作成と破棄 が可能です。 Javaを使用すると、移植性のあるクロスプラットフォーム方式でスレッドを簡単に作成できます。

常にスレッドを作成および破棄するにはコストがかかる傾向があるため、Javaには Executors が含まれ、再利用可能なスレッドプールが作成されます。タスクはエグゼキューターに割り当てることができます、および結果はFutureオブジェクトを介して取得できます。

通常、1つには小さなタスクに分割できるタスクがありますが、最終結果を一緒に戻す必要があります。たとえば、マージソートでは、すべてのコアがソートを実行するまで、リストをより小さな部分に分割できます。ただし、各サブリストはソートされているため、最終的なソート済みリストを取得するには、サブリストをマージする必要があります。これは「分割統治」の問題であることがかなり一般的であるため、基礎となる配布と参加を処理できる JSRフレームワーク があります。このフレームワークは、おそらくJava 7。

8
brianegge

JavaでCPUアフィニティを設定する方法はありません。 http://bugs.Sun.com/bugdatabase/view_bug.do?bug_id=4234402

必要な場合は、JNIを使​​用してネイティブスレッドを作成し、それらのアフィニティを設定します。

4
Iouri Goussev

lot Callableの形式でExecutorServiceに渡され、invokeAll(...)で実行されるプログラムを作成する必要があります。

その後、Executorsクラスから実行時に適切な実装を選択できます。提案は、ビジー状態を保つためにCPUコアの数にほぼ対応する数でExecutors.newFixedThreadPool()を呼び出すことです。

以下のAPIを使用できます Executors with Java 8 version

public static ExecutorService newWorkStealingPool()

使用可能なすべてのプロセッサーをそのターゲット並列処理レベルとして使用して、ワークスチールスレッドプールを作成します。

ワークスチールメカニズムにより、アイドルスレッドはビジースレッドのタスクキューからタスクをスチールし、全体的なスループットが増加します。

grepcode から、newWorkStealingPoolの実装は次のとおりです。

/**
     * Creates a work-stealing thread pool using all
     * {@link Runtime#availableProcessors available processors}
     * as its target parallelism level.
     * @return the newly created thread pool
     * @see #newWorkStealingPool(int)
     * @since 1.8
     */
    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
1
Ravindra babu

JVMのパフォーマンスチューニングについては、以前に なぜJavaコードはすべてのCPUコアを使用しないのですか? で説明されています。これはJVMにのみ適用されるため、アプリケーションは既にスレッドを使用している(そして、多少なりとも「正しく」):

http://ch.Sun.com/sunnews/events/2009/apr/adworkshop/pdf/5-1-Java-Performance.pdf

1
ShiDoiSi

最も簡単なことは、プログラムを複数のプロセスに分割することです。 OSはそれらをコア全体に割り当てます。

プログラムを複数のスレッドに分割し、JVMがそれらを適切に割り当てることを信頼することは、やや困難です。これは、一般に、利用可能なハードウェアを利用するために人々が行うことです。


編集

マルチプロセッシングプログラムを「簡単に」するにはどうすればよいですか?これがパイプラインのステップです。

public class SomeStep {
    public static void main( String args[] ) {
        BufferedReader stdin= new BufferedReader( System.in );
        BufferedWriter stdout= new BufferedWriter( System.out );
        String line= stdin.readLine();
        while( line != null ) {
             // process line, writing to stdout
             line = stdin.readLine();
        }
    }
}

パイプラインの各ステップは同様に構造化されています。どんな処理が含まれていても、9行のオーバーヘッド。

これは絶対的に最も効率的ではないかもしれません。しかし、それは非常に簡単です。


並行プロセスの全体的な構造は、JVMの問題ではありません。 OSの問題なので、シェルを使用してください。

Java -cp pipline.jar FirstStep | Java -cp pipline.jar SomeStep | Java -cp pipline.jar LastStep

残っているのは、パイプライン内のデータオブジェクトのシリアル化を行うことだけです。標準シリアル化はうまく機能します。 http://Java.Sun.com/developer/technicalArticles/Programming/serialization/ を読んで、シリアル化の方法に関するヒントを確認してください。これを行うには、BufferedReaderBufferedWriterObjectInputStreamObjectOutputStreamに置き換えることができます。

1
S.Lott

この問題はJava Parallel Proccesing Framework(JPPF)に関連していると思います。これを使用すると、異なるプロセッサで異なるジョブを実行できます。

1
Nandika