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]への回答は、キャストが間違っていることを示唆しています(そして私は同意します)。しかし、確かに標準はそのような一見安全なアクセスを許可していますか?
より一般的には、リーダーライターロックを使用して、「リーダー」がアトミックに読み書きできるようにし、一意の「ライター」が非アトミックに読み書きできるようにすることはできますか?