2012-05-05 23 views
1

たとえば、複数のスレッドで同時に計算される作業があります。安全なマルチスレッドカウンタインクリメント

デモンストレーション目的で、作業はwhileループ内で実行されます。 1回の反復では、各スレッドは作業の独自の部分を実行し、次の反復が始まる前にカウンタを1回インクリメントする必要があります。

私の問題は、カウンタが各スレッドによって更新されることです。

これはやりたいことが比較的簡単なように思えるので、私は「ベストプラクティス」やそれを実行する一般的な方法があると推測します。

ここでは、問題を説明し、説明に役立つサンプルコードを示します。 (ブーストスレッドを使用してイム)

class someTask { 
public: 
    int mCounter; //initialized to 0 
    int mTotal; //initialized to i.e. 100000 
    boost::mutex cntmutex;     
    int getCount() 
    { 
      boost::mutex::scoped_lock lock(cntmutex); 
      return mCount; 
    } 
    void process(int thread_id, int numThreads) 
    { 
     while (getCount() < mTotal) 
     { 
      // The main task is performed here and is divided 
      // into sub-tasks based on the thread_id and numThreads 

          // Wait for all thread to get to this point 

      cntmutex.lock(); 
      mCounter++; // < ---- how to ensure this is only updated once? 
      cntmutex.unlock(); 
     } 
    } 
}; 
+0

それよりももっとあります:それは、スレッドがお互いを待つことが必要でしょうか?指定された*マスター*スレッドはありませんか?あるいは何もしないで待っているスケジューラでしょうか? –

+0

@MatthieuM ahはい、インクリメントが実行される前にすべてのスレッドがその作業を完了する必要があります。 – AlexS

+0

あなたの編集は間違っています:与えられた変数セットへのすべてのアクセスに同じロックを使用する必要があります。したがってここではロックは1つだけ必要です。 –

答えて

2

あなたは何の2つのスレッドが同時にカウンタにアクセスできないことを保証し、ミューテックスでカウンターを保護しました。他のオプションは、Boost::atomic,c++11 atomic operationsまたはプラットフォーム固有のアトミック操作を使用します。

しかし、あなたのコードは、ミューテックスを保持せずmCounterにアクセスするようだ:問題です

while (mCounter < mTotal) 

を。共有状態にアクセスするには、ミューテックスを保持する必要があります。

  1. 獲得ロック:

    あなたはこのイディオムを使用することを好むことがあります。

  2. 私たちが仕事をする必要があるかどうかを判断するためのテストなどを行います。

  3. 私たちがやったことを反映して会計処理を調整します。

  4. ロックを解除します。仕事する。ロックを取得する。

  5. 私たちが行った作業を反映するように調整します。

  6. 私たちが完全に完了していない限り、ループはステップ2に戻ります。

  7. ロックを解除します。

+0

良い点mCounter私は '本当の'コードでこれを世話しました。私は何かを入れ、原子をチェックします。 – AlexS

+0

私は申し訳ありませんが、私はあなたが投稿したイディオムを理解していないか、または私が現在持っているものとはどのように異なっているかを正確には分かりません。 – AlexS

+1

@AlexS:アイデアは、時間がかかる(実際の作業や作業を待っている)コードの部分を分離して、ロック解除/ロックのペアでラップすることです。他のすべての時に、あなたはロックを保持します。これは、ロックを獲得するという通常のパターンを逆転させ、何かを素早く行い、ロックを解放するパターンにリリースし、遅いものを実行してから、再度取得します。 –

5

私がここで見る主な問題は、あまりにも低いレベルで推論することです。したがって、新しいC++ 11スレッドAPIに基づく代替ソリューションを提示する予定です。

主な考え方は、本質的にschedule - > dispatch - > do - > collect - > loopルーチンがあることです。あなたの例では、非常に難しいdoフェーズ内のすべてについてこれを推論しようとします。あなたのパターンは、反対のアプローチを使用してはるかに簡単に表現することができます。我々は独自のルーチンに行うべき作業隔離

まず:

void process_thread(size_t id, size_t numThreads) { 
    // do something 
} 

を、我々は簡単にこのルーチンを呼び出すことができます。

#include <future> 
#include <thread> 
#include <vector> 

void process(size_t const total, size_t const numThreads) { 
    for (size_t count = 0; count != total; ++count) { 
     std::vector< std::future<void> > results; 

     // Create all threads, launch the work! 
     for (size_t id = 0; id != numThreads; ++id) { 
      results.push_back(std::async(process_thread, id, numThreads)); 
     } 

     // The destruction of `std::future` 
     // requires waiting for the task to complete (*) 
    } 
} 

(*)this questionを参照してください。

std::asynchereについては、詳しくは読むことができます。短い紹介はoffered hereです(これらは起動ポリシーの影響でやや矛盾しているようです)。 OSスレッドを作成するかどうかを実装が決定できるようにする方が簡単です。使用可能なコアの数に応じて適応することができます。

によってコードがどのように簡略化されているかをご確認ください。共有状態。スレッドは何も共有しないので、明示的に同期を心配する必要はありません。

+0

私が投稿した質問に完全に良い解決策があります。 (私は低レベルのソリューションを好むとは言えますが、何が起こっているのか正確に知っているからと言っています。)「結果」が結果ベクトルにどのようにプッシュされているのか分かりません。 – AlexS

+0

一方、タスクがスレッドを作成しているので、これは私が望むものではありませんが、待ち行列内のすべてのタスクが完了するまで、待ち時間のないタスクがない限り、タスクを処理する有限のスレッドプールがあります。いくつかのタスクは1つのスレッドによって実行されるように指定され、いくつかのタスクは複数のスレッドによって実行されます。希望が合った。 – AlexS

+0

@AlexS: 'ベクトル'の考え方は、 'future'を(移動構造を使って)保存して、その破壊が遅れるようにすることです。これにより、複数の並行タスクが作成され、並行して実行される可能性があります。 'async'に関しては実際に必ずしもスレッドを作成するわけではありません。新しいスレッドを明示的に要求する最初のパラメータとして' std :: launch :: async'を使用する必要があります。スレッドを作成するかどうかを選択します。良いランタイムは、独自のプールを維持する可能性が高い... –

0

メッセージパッシングソリューションを使用する必要があります。これは、TBBやPPLなどのライブラリによって簡単に有効になります。 Visual Studio 2010以上ではPPLが無料で、TBBはIntelのFOSSライセンスで無料でダウンロードできます。

concurrent_queue<unsigned int> done; 
std::vector<Work> work; 
// fill work here 
parallel_for(0, work.size(), [&](unsigned int i) { 
    processWorkItem(work[i]); 
    done.push(i); 
}); 

それはロックなしだとあなたは、外部のスレッドは、完了しているどのくらい、とものを見るためにdone変数を監視することができます。

0

私はDavidが仕事をするために複数のロックを取得することに同意したいと思います。

Mutexesは、mutexのために競合するスレッドが増え、基本的にシステムコールに戻って、呼び出し元のスレッド(/ s)とともに強制的にカーネルスペースのコンテキストスイッチを切り替えることになります。したがって、多くのオーバーヘッド。

マルチプロセッサシステムを使用している場合は、[1]の代わりにスピンロックを使用することを強くお勧めします。

だから、私は何をするだろうことは次のとおりです。

=>は、状態を確認するスコープロックの取得を取り除きます。

=>

上でサポートするために、カウンタは揮発してください=> whileループでロックを取得した後、再度、条件チェックを行います。

class someTask { 
public: 
volatile int mCounter; //initialized to 0  : Make your counter Volatile 
int mTotal; //initialized to i.e. 100000 
boost::mutex cntmutex;     

void process(int thread_id, int numThreads) 
{ 
    while (mCounter < mTotal) //compare without acquiring lock 
    { 
     // The main task is performed here and is divided 
     // into sub-tasks based on the thread_id and numThreads 

     cntmutex.lock(); 
     //Now compare again to make sure that the condition still holds 
     //This would save all those acquisitions and lock release we did just to 
     //check whther the condition was true. 
     if(mCounter < mTotal) 
     { 
      mCounter++; 
     } 

     cntmutex.unlock(); 
    } 
} 
}; 

[1] http://www.alexonlinux.com/pthread-mutex-vs-pthread-spinlock

+0

私はこれがうまくいくとは思っていません。作業が終わったら、複数のスレッドがmCounterをインクリメントします。私は何かを逃していない限り??? – AlexS

+0

それは動作します!ロックレス比較はオーバーヘッドを回避し、ロックされた比較は増分の健全性を強制します。次に、揮発性変数とのロックレス比較は、すべてのスレッドが変数の同じコピーを参照するようにします。 –

+0

しかし、一方のスレッドがmutexのロックを解除すると、もう一方のスレッドはロックしてmCounter ++、 – AlexS