8

std::atomic 型は変数へのアトミック アクセスを許可しますが、アクセスがミューテックスによって保護されている場合など、非アトミック アクセスが必要な場合があります。マルチスレッド アクセス (insert 経由) とシングルスレッド ベクトル化アクセス (operator|= 経由) の両方を許可するビットフィールド クラスを考えてみましょう。

class Bitfield
{
    const size_t size_, word_count_;
    std::atomic<size_t> * words_;
    std::mutex mutex_;

public:

    Bitfield (size_t size) :
        size_(size),
        word_count_((size + 8 * sizeof(size_t) - 1) / (8 * sizeof(size_t)))
    {
        // make sure words are 32-byte aligned
        posix_memalign(&words_, 32, word_count_ * sizeof(size_t));
        for (int i = 0; i < word_count_; ++i) {
            new(words_ + i) std::atomic<size_t>(0);
        }
    }
    ~Bitfield () { free(words_); }

private:
    void insert_one (size_t pos)
    {
        size_t mask = size_t(1) << (pos % (8 * sizeof(size_t)));
        std::atomic<size_t> * word = words_ + pos / (8 * sizeof(size_t));
        word->fetch_or(mask, std::memory_order_relaxed);
    }
public:
    void insert (const std::set<size_t> & items)
    {
        std::lock_guard<std::mutex> lock(mutex_);
        // do some sort of muti-threaded insert, with TBB or #pragma omp
        parallel_foreach(items.begin(), items.end(), insert_one);
    }

    void operator |= (const Bitfield & other)
    {
        assert(other.size_ == size_);
        std::unique_lock<std::mutex> lock1(mutex_, defer_lock);
        std::unique_lock<std::mutex> lock2(other.mutex_, defer_lock);
        std::lock(lock1, lock2); // edited to lock other_.mutex_ as well
        // allow gcc to autovectorize (256 bits at once with AVX)
        static_assert(sizeof(size_t) == sizeof(std::atomic<size_t>), "fail");
        size_t * __restrict__ words = reinterpret_cast<size_t *>(words_);
        const size_t * __restrict__ other_words
            = reinterpret_cast<const size_t *>(other.words_);
        for (size_t i = 0, end = word_count_; i < end; ++i) {
            words[i] |= other_words[i];
        }
    }
};

operator|= は私の実際のコードに非常に近いことに注意してください。

acquire lock;
make many atomic accesses in parallel;
release lock;

私の質問は次のとおりです。そのようなアトミック アクセスと非アトミック アクセスを混在させる最善の方法は何ですか? 以下の[1,2]への回答は、キャストが間違っていることを示唆しています(そして私は同意します)。しかし、確かに標準はそのような一見安全なアクセスを許可していますか?

より一般的には、リーダーライターロックを使用して、「リーダー」がアトミックに読み書きできるようにし、一意の「ライター」が非アトミックに読み書きできるようにすることはできますか?

参考文献

  1. std::atomic を効率的に使用する方法
  2. 非アトミックとして C++0x のアトミック <int> にアクセスする
4

3 に答える 3

5

C++11 より前の標準 C++ には、マルチスレッド メモリ モデルがありませんでした。非アトミック アクセスのメモリ モデルを定義する標準の変更は見られないため、C++11 より前の環境と同様の保証が得られます。

memory_order_relaxed非アトミック アクセスのクロス スレッド動作は、複数の可能な実行順序が最終的に発生する必要があるのとは対照的に、単に完全に定義されていないため、実際には理論的には を使用するよりもさらに悪いことです。

そのため、アトミック アクセスと非アトミック アクセスを混在させながらこのようなパターンを実装するには、プラットフォーム固有の非標準構造 (たとえば_ReadBarrier) や特定のハードウェアに関する詳細な知識に依存する必要があります。

より良い代替手段は、列挙型に慣れmemory_order、特定のコードとコンパイラで最適なアセンブリ出力を実現することです。最終結果は正しく、移植性があり、不要なメモリ フェンスが含まれていない可能性があります。また、すべてのコード パスでアトミック アクセスを使用しても、別のアーキテクチャや別のコンパイラで不要なフェンスが発生しないという保証はまだありません。

したがって、最も実用的な答えは、最初に単純化することです。スケーラビリティ、応答性、またはその他の聖なる牛を完全に殺すことなく、クロススレッドの相互作用をできる限りシンプルに設計します。可変データ構造を共有することはほとんどありません。可能な限りめったにアクセスせず、常にアトミックにアクセスします。

于 2012-09-02T19:59:26.480 に答える
4

これを行うことができれば、(潜在的に) 1 つのスレッドがアトミック アクセスを使用してデータ オブジェクトの読み取り/書き込みを行い、別のスレッドがアトミック アクセスを使用せずに同じデータ オブジェクトの読み取り/書き込みを行うことになります。これはデータ競合であり、動作は未定義です。

于 2012-09-02T19:26:15.843 に答える