2016-10-07 4 views
2

mutexブロックの外側でも変数を強制的にメモリに書き込む際には、何か問題があります。 readerのデータが失効しているかどうかを判断するロジックを取り除いたので、以下の複雑なコードについてはお詫び申し上げます。注目すべき重要なことは、時間の99.9%で、読者はfast pathをとり、同期は非常に高速でなければならないということです。なぜなら、アイドルint32を使用して、失速と低速パスが現在必要かどうかを通信するのです。C++の並行性:mutex以外での変数の可視性

#define NUM_READERS 10 

BigObject    mSharedObject; 
std::atomic_int32_t  mStamp = 1; 
std::mutex    mMutex; 
std::condition_variable mCondition; 
int32_t     mWaitingReaders = 0; 

void reader() { 
    for (;;) { // thread loop 
     for (;;) { // spin until stamp is acceptible 
      int32_t stamp = mStamp.load(); 
      if (stamp > 0) { // fast path 
       if (stampIsAcceptible(stamp) && 
        mStamp.compare_exchange_weak(stamp, stamp + 1)) { 
        break; 
       } 
      } else { // slow path 
       // tell the loader (writer) that we're halted 
       std::unique_lock<mutex> lk(mMutex); 
       mWaitingReaders++; 
       mCondition.notify_all(); 
       while (mWaitingReaders != 0) { 
        mCondition.wait(lk); 
       } // ### 
       lk.unlock(); 
       // *** THIS IS WHERE loader's CHANGES TO mSharedObject 
       // *** MUST BE VISIBLE TO THIS THREAD! 
      } 
     } 
     // stamp acceptible; mSharedObject guaranteed not written to 

     mSharedObject.accessAndDoFunStuff(); 

     mStamp.fetch_sub(1); // part of hidden staleness logic 
    } 
} 

void loader() { 
    for (;;) { // thread loop 
     // spin until we somehow decide we want to change mSharedObject! 
     while (meIsHappySleeping()) {} 

     // we want to modify mSharedObject, so set mStamp to 0 and wait 
     // for readers to see this and report that they are now waiting 
     int32_t oldStamp = mStamp.exchange(0); 
     unique_lock<mutex> lk(mMutex); 
     while (mWaitingReaders != NUM_READERS) { 
      mCondition.wait(lk); 
     } 
     // all readers are waiting. start writing to mSharedObject 
     mSharedObject.loadFromFile("example.foo"); 
     mStamp.store(oldStamp); 
     mWaitingReaders = 0; // report completion 
     lk.unlock(); 
     mCondition.notify_all(); 
     // *** NOW loader's CHANGES TO mSharedObject 
     // *** MUST BE VISIBLE TO THE READER THREADS! 
    } 
} 

void setup() { 
    for (int i = 0; i < NUM_READERS; i++) { 
     std::thread t(reader); t.detach(); 
    } 
    std::thead t(loader); t.detach(); 
} 

***でマーク部分が私に関係するものです。

が、私は私は次のセットアップを持っている「かなり」特定のレースフリーです。私のコードではレースを除外していますが(私が見る限り)、mSharedObjectloader()によって書き込まれている間だけミューテックスによって保護されているためです。 reader()は(上記のように)非常に高速である必要があるため、私はその読み取り専用アクセスをmSharedObjectにミューテックスで保護する必要はありません。

const BigObject *latestObject」の###のスレッドローカル変数を導入することです。これは&mSharedObjectに設定され、アクセスに使用されます。しかし、この悪い習慣は?本当に必要ですか?アトミック操作/ mutexのリリース操作によって、読者は変更を確認できますか?

ありがとうございます!

答えて

1

アトミックを使用したロックフリーコードとロックコードでさえ、単純なものとはまったく異なります。最初に行うことは、ミューテックスを追加して、同期で実際にどれだけのパフォーマンスが失われたかをプロファイルすることです。現時点でのmutexの実装では、スピンロックをすばやく行うことができます。これは、競合していない場合のアトミック操作です。

ロックフリーのプログラミングを試したい場合は、アトミック操作のメモリ順序付け引数を調べる必要があります。作者は、..._acquireを実行するリーダーと同期するには、..._releaseにする必要があります(または、両側に順次整合性を使用します)。そうしないと、他の変数への読み込み/書き込みが見えなくなることがあります。

+0

はい、私はこれについての研究に時間を費やしていませんでした...私はそれについて少しのメモをしました:http://imgur.com/a/Dpx46 今私の原子操作はすべてですデフォルトの順次整合性。あなたの答えには違いがありますか? – bombax

+0

私はちょうど何か気づいた。読者の '*** 'セグメントの後、ループバックし、' mSharedObject.accessAndDoFunStuff() 'を呼び出す前に' mStamp'に逐次一貫してアクセスします。しかし、 'mStamp'は' loadFromFile() 'の直後に' loader() 'によって書かれたseq-conです。これは 'reader()'に 'mSharedObject'への変更を見せてもらうのに十分であるように感じます。私は正しい?ありがとう!! – bombax