素朴なブール否定
std::atomic_bool b;
b = !b;
アトミックではないようです。これはoperator!
はプレーンbool
へのキャストをトリガーします。同等の否定をどのようにアトミックに実行しますか?次のコードは、単純な否定がアトミックではないことを示しています。
#include <thread>
#include <vector>
#include <atomic>
#include <iostream>
typedef std::atomic_bool Bool;
void flipAHundredThousandTimes(Bool& foo) {
for (size_t i = 0; i < 100000; ++i) {
foo = !foo;
}
}
// Launch nThreads std::threads. Each thread calls flipAHundredThousandTimes
// on the same boolean
void launchThreads(Bool& foo, size_t nThreads) {
std::vector<std::thread> threads;
for (size_t i = 0; i < nThreads; ++i) {
threads.emplace_back(flipAHundredThousandTimes, std::ref(foo));
}
for (auto& thread : threads) thread.join();
}
int main() {
std::cout << std::boolalpha;
Bool foo{true};
// launch and join 10 threads, 20 times.
for (int i = 0; i < 20; ++i) {
launchThreads(foo, 10);
std::cout << "Result (should be true): " << foo << "\n";
}
}
このコードは10個のスレッドを起動し、各スレッドはatomic_boolを偶数回(100000)反転させ、ブール値を出力します。これを20回繰り返します。
[〜#〜] edit [〜#〜]:このコードを実行したい人のために、2つのコアを備えたubuntu11.10でGCC4.7スナップショットを使用しています。コンパイルオプションは次のとおりです。
-std=c++0x -Wall -pedantic-errors -pthread
_b = !b
_はアトミックではありません。これは、C++ソースでb
(b.load()
と同等)のアトミックな純粋な読み取りがあり、b
への個別のアトミックな割り当てがあるためです。 (b.store()
と同等)。
組み合わせ全体をC++抽象マシンのアトミックRMW操作にするものはなく、任意の操作をアトミックRMW操作に構成するための構文もありません(CAS再試行ループに入れる以外)。
使用するオプションは2つあります。
_atomic<bool>
_の代わりに、0または1の整数型(例:_atomic<int>
_または_atomic<unsigned char>
_)を使用し、1でxorします。
_std::atomic<int> flag(0);
flag ^= 1; //equivalent to flag.fetch_xor(1);
_
残念ながら、_fetch_xor
_は_atomic<bool>
_では提供されず、整数型でのみ提供されます。
成功するまで、ループ内で比較/交換操作を実行します。
_std::atomic<bool> flag(false);
bool oldValue = flag.load();
while (!flag.compare_exchange_weak(oldValue, !oldValue)) {}
_
残念ながら、x86のコンパイラは通常、このループを最適化して
_lock xor byte [flag], 1
_ asm;実際のcmpxchg再試行ループが発生します。実際には、cmpxchgの再試行ループは、競合が少なくても問題ありません。最悪の場合、これは待機なしではありませんが、すべてが再試行するたびに少なくとも1つのスレッドが進行するため、ロックなしです。 (実際には、コアがキャッシュラインにアクセスして試行するハードウェアアービトレーションではより複雑になります。)
高い競合が発生する可能性がある場合は、アトミックxorを使用できる整数バージョンを選択してください。