2011-07-01 5 views
3

私のプロジェクトでは、今までCyclicBarrierを使用して複数のスレッド(それぞれが同じタイプのRunnableを実行中)を「同期」させています。私の場合、CyclicBarrierを使用すると同期の頻度が高いため非効率であることが判明しましたが、ビジー待機メカニズムがより速く動作する可能性があります。ここでは(いくつかの部分が出て左)私がこれまでに得たものです:Java - ビジー待機機構を実装する

public class MyRunnable implements Runnable { 

    private static AtomicInteger counter = null; // initialized to the number 
               // of threads 

    public void run() { 

     // do work up to a "common point" 

     synchronized (this) { 

      // decrement the counter and - if necessary - reset it 
      if (counter.decrementAndGet() == 0) { 

       counter.set(numberOfThreads); 

       // make all the busy waiting threads exit from the loop 
       for (int i = 0; i < threads.length; i++) 
        threads[i].interrupt(); 
      } 
     } 

     // busy wait until all threads have reached the "common point" 
     while (!Thread.interrupted()) {} 
    } 
} 

残念ながら、このコードはCyclicBarrierよりもさらに悪い行います。 Here's短いコンパイル可能な例。どのようにそれを改善するための任意の提案?

+1

これらは、コードレビュー(http://codereview.stackexchange.com/)に最適な質問のようですが、そこを尋ねてみましたか? –

+1

AtomicIntegerを使用してスレッドオブジェクトitslefで同期する場合、なぜsynchronizedブロックが必要ですか? – Asaf

+0

@Asaf:今言えば、本当の理由はありません。しかし、同期化されていないと遅くなります。 – ryyst

答えて

3

ここでビジー待機は、より多くのプロセッサを搭載している場合には「高速」に動作し、スレッドを実行しています。あなたがThread.interruptedを継続的に回転させ、CPU時間を消費しているだけでは、実際にはパフォーマンスが大幅に低下します。

CyclicBarrier/CountDownLatchで何が問題になりましたか?それははるかに良い解決策のように思えます。

+0

スレッドは同期ポイント間での作業が比較的少ないため、CyclicBarrierのオーバーヘッドはすべてを低速化しました。 (ディスカッション[こちら](http://stackoverflow.com/questions/6541104/java-multithreaded-code-does-not-run-faster-on-more-cores)を参照してください) – ryyst

0

待機/通知はどうですか?一般的なノートで

public class MyRunnable implements Runnable { 

    private static AtomicInteger counter = null; // initialized to the number 
               // of threads 

    public void run() { 

     // do work up to a "common point" 

     // need to synchronize for wait/notify. 
     synchronized (counter) { 
      // decrement the counter and - if necessary - reset it 
      if (counter.decrementAndGet() == 0) { 

       counter.set(numberOfThreads); 

       // notify all the waiting threads 
       counter.notifyAll(); 
      }else{ 
       // wait until all threads have reached the "common point" 
       counter.wait(); 
      } 
     } 
    } 
} 

あなたはそれほど頻繁に障壁のオーバーヘッドが問題であることを同期している場合、それは疑わしいです:あなたはマルチスレッド価値はない仕事をやっているか、あなたはより頻繁同期していますあなたはすべきです。

+0

あなたのインプリメンテーションはカウンタでの同期が不十分で、 'IllegalMonitorStateException'をスローします。 – ryyst

+0

オブジェクトのモニタを保持していない限り、 'notifyAll()'や 'wait()'を呼び出すことはできません。 –

+0

@ryyst:修正されました。私は通常 'wait()'と '[All]()'を直接使用しないので、ロックが最初にどのように動作するかはわかりませんでした。 – trutheality

1

どうやってこのようなことができますか?このコードには同時実行のバグがあります(1つのスレッドがcounter.get()への呼び出しの間に遅い場合)が、2つのカウンタを持ち、このコードを2回繰り返すことで解決できます。

if (counter.decrementAndGet() == 0) { 
    counter.set(numberOfThreads); 
} else { 
    while (counter.get() < numberOfThreads) {} 
} 

パフォーマンス問題を示すコンパイル可能な例を掲載してください。さもなければ、すべての答えはちょうど投機であろう。

+0

[ここをクリック](http:// pastebin.com/4J56v8ii)。この問題は、実行されている同期の数に比較して、作業負荷がやや小さいことが原因です。 – ryyst

+0

このコードの問題は、*どの*作業もしていないことです。 JVMは計算を最適化するので、残っているのは同期です。これを最初に理解しておいてください:http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java –

+0

ベンチマークに欠陥がありますが、この2つのカウンタを持つコードは2〜16倍高速(スレッド数に応じて、C2Q6600では1〜4スレッドをテストしました)。しかし、それはさらに多くのスレッドで悪化します。これは、同期が常にオーバーヘッドを引き起こすため、予想されます。 –

1

ビジー待機ループが非ビジーループより速いとは想像もつきません。まず、あなたのコードでは、CyclicBarrier(以下を参照)を使用するときに必要とする同期をまだ使用しています。次に、CyclicBarrierメカニズムを再実装しました。このメカニズムでは、Java開発者が最適なパフォーマンスを得るために時間と労力を費やしています。第3に、CyclicBarrierは同期のためにReentrantLockを使用します。これは明らかに​​キーワードを使用するより効率的で高速です。全体的には、あなたのコードがレースに勝つことはまずありません。このコードでは、ビジー待機とあなたのバージョンでは以上のものではありませんこれは、thread.length回を同期します、1回の実行で

public class MyRunnable implements Runnable { 

    private static CyclicBarrier barrier = new CyclicBarrier(threads.length); 

    public void run() { 

     // do work up to a "common point" 

     try{ 
      barrier.await(); 
     }catch(InterruptedException e){ 
      Thread.interrupt(); 
      //Something unlikely has happened. You might want to handle this. 
     } 
    } 
} 

は、このリファレンスコードを考えてみましょう。したがって、あなたのコードより遅くすることはできません。

パフォーマンス上の問題の本当の原因は、スレッドが「会う」前にスレッドがほとんど機能しないことです。これは、おそらく、スレッドのコンテキスト切り替えのオーバーヘッドと同期の回数が多いことを意味します。

アーキテクチャを再検討できますか?あなたは本当にすべての労働者が共通点で「会う」のを待つ必要がありますか?彼らの "会議"の前にもう少し仕事をすることができますか?スレッド数をより少ない数(= CPU /コア数)に設定しようとしましたか?

コードの目的についてもう少し詳しくお聞かせください。もう少し詳しくお聞かせください。

+0

補足として。 Reentrantlockは、同期した場合と比べてはるかに高速ではありません。それはjava 6に当てはまります –

関連する問題