1

ベアボーンで実行されていたプロジェクトを Linux に移行しており、いくつかの{disable,enable}_scheduler呼び出しを削除する必要があります。:)

したがって、ライター スレッドをブロックできない単一のライター、複数のリーダーのシナリオでは、ロックのない同期ソリューションが必要です。次の解決策を思いつきましたが、これは通常の取得と解放の順序には適合しません。

class RWSync {
    std::atomic<int> version; // incremented after every modification
    std::atomic_bool invalid; // true during write
public:
  RWSync() : version(0), invalid(0) {}
  template<typename F> void sync(F lambda) {
    int currentVersion;
    do {
      do { // wait until the object is valid
        currentVersion = version.load(std::memory_order_acquire);
      } while (invalid.load(std::memory_order_acquire));
      lambda();
      std::atomic_thread_fence(std::memory_order_seq_cst);
      // check if something changed
    } while (version.load(std::memory_order_acquire) != currentVersion
        || invalid.load(std::memory_order_acquire));
  }
  void beginWrite() {
    invalid.store(true, std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_seq_cst);
  }
  void endWrite() {
    std::atomic_thread_fence(std::memory_order_seq_cst);
    version.fetch_add(1, std::memory_order_release);
    invalid.store(false, std::memory_order_release);
  }
}

意図が明確であることを願っています: (非アトミック) ペイロードの変更を でラップし、 にbeginWrite/endWrite渡されたラムダ関数内でのみペイロードを読み取りますsync()

ご覧のとおり、ストア操作後の書き込みをストアの前に並べ替えることができないアトミックストアがあります。beginWrite()適切な例が見つからず、この分野の経験がまったくないため、問題がないことを確認したいと思います(テストによる検証も容易ではありません)。

  1. このコードはレースフリーで、期待どおりに動作しますか?

  2. すべてのアトミック操作で std::memory_order_seq_cst を使用する場合、フェンスを省略できますか? (そうだとしても、パフォーマンスは悪くなると思います)

  3. endWrite() でフェンスをドロップできますか?

  4. フェンスで memory_order_acq_rel を使用できますか? 私には違いがよくわかりません。単一の合計注文の概念は私には明確ではありません。

  5. 簡素化/最適化の機会はありますか?

+1。このクラスの名前として、より良いアイデアを喜んで受け入れます:)

4

2 に答える 2

1

コードは基本的に正しいです。

version2 つのアトミック変数 (およびinvalid) を使用する代わりに、「奇数の値は無効です」という意味を持つ単一の変数を使用できます。 versionこれは「シーケンシャル ロック」メカニズムとして知られています。

アトミック変数の数を減らすと、物事が大幅に簡素化されます。

class RWSync {
    // Incremented before and after every modification.
    // Odd values mean that object in invalid state.
    std::atomic<int> version; 
public:
  RWSync() : version(0) {}
  template<typename F> void sync(F lambda) {
    int currentVersion;
    do {
      currentVersion = version.load(std::memory_order_seq_cst);
      // This may reduce calls to lambda(), nothing more
      if(currentVersion | 1) continue;

      lambda();

      // Repeat until something changed or object is in an invalid state.
    } while ((currentVersion | 1) ||
        version.load(std::memory_order_seq_cst) != currentVersion));
  }
  void beginWrite() {
    // Writer may read version with relaxed memory order
    currentVersion = version.load(std::memory_order_relaxed);
    // Invalidation requires sequential order
    version.store(currentVersion + 1, std::memory_order_seq_cst);
  }
  void endWrite() {
    // Writer may read version with relaxed memory order
    currentVersion = version.load(std::memory_order_relaxed);
    // Release order is sufficient for mark an object as valid
    version.store(currentVersion + 1, std::memory_order_release);
  }
};

と のメモリ順序の違いに注意してbeginWrite()くださいendWrite()

  • endWrite()以前のオブジェクトの変更がすべて完了していることを確認します。そのためには、解放メモリ順序を使用すれば十分です。

  • beginWrite()オブジェクトの変更が開始される前に、オブジェクトが無効な状態にあることをリーダーが確実に検出できるようにします。このような保証には、seq_cstメモリ順序が必要です。そのため、リーダーはseq_cstメモリ オーダーも使用します。

フェンスに関しては、それらを前/後のアトミック操作に組み込むことをお勧めします。コンパイラは結果を高速にする方法を知っています。


元のコードのいくつかの変更の説明:

1)のようなアトミックな変更は、同時変更(別の のような) が可能なfetch_add()場合を対象としています。正確を期すために、このような変更では、メモリのロックまたはその他の非常に時間のかかるアーキテクチャ固有のものを使用します。fetch_add()

アトミック代入( store()) はメモリ ロックを使用しないため、より安価ですfetch_add()。あなたのケースでは同時変更が不可能であるため、このような割り当てを使用できます(リーダーは変更しませんversion)。

2)操作を区別するリリース取得セマンティックloadとは異なりstore、順次一貫性 ( memory_order_seq_cst) はすべてのアトミック アクセスに適用され、これらのアクセス間の全体的な順序を提供します。

于 2017-01-11T09:30:48.523 に答える