あなたの使用法はあなたがあなたのコメントで言及することを実際に保証するものではありません。つまり、フェンスを使用しても、への割り当てa
が他のスレッドに表示されることや、読み取り元の値a
が「最新」であることは保証されません。これは、フェンスを使用する場所についての基本的な考え方はあるように見えますが、コードが実際にはそれらのフェンスが「同期」するための正確な要件を満たしていないためです。
これは、正しい使用法をよりよく示していると私が思う別の例です。
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<bool> flag(false);
int a;
void func1()
{
a = 100;
atomic_thread_fence(std::memory_order_release);
flag.store(true, std::memory_order_relaxed);
}
void func2()
{
while(!flag.load(std::memory_order_relaxed))
;
atomic_thread_fence(std::memory_order_acquire);
std::cout << a << '\n'; // guaranteed to print 100
}
int main()
{
std::thread t1 (func1);
std::thread t2 (func2);
t1.join(); t2.join();
}
アトミックフラグのロードとストアは、どちらも緩和されたメモリ順序を使用するため、同期しません。フェンスがなければ、このコードはデータ競合になります。これは、異なるスレッドで非アトミックオブジェクトの競合する操作を実行しているためです。また、フェンスとそれらが提供する同期がないと、の競合する操作間の関係が発生する前に発生することはありませんa
。
ただし、フェンスを使用すると、スレッド2がスレッド1によって書き込まれたフラグを読み取ることが保証されているため(その値が表示されるまでループするため)、リリースフェンスの後にアトミック書き込みが行われ、アトミック読み取りが行われるため、同期が行われます。 -取得フェンスの前に、フェンスは同期します。(特定の要件については、§29.8 / 2を参照してください。)
この同期とは、リリースフェンスが発生する前に、取得フェンスの後に発生するすべてのことを意味します。したがって、非アトミック書き込みがa
発生します-の非アトミック読み取りの前にa
。
ループ内で変数を記述している場合、特定の反復では発生前の関係を確立できますが、他の反復では確立できず、データ競合が発生する可能性があるため、状況はさらに複雑になります。
std::atomic<int> f(0);
int a;
void func1()
{
for (int i = 0; i<1000000; ++i) {
a = i;
atomic_thread_fence(std::memory_order_release);
f.store(i, std::memory_order_relaxed);
}
}
void func2()
{
int prev_value = 0;
while (prev_value < 1000000) {
while (true) {
int new_val = f.load(std::memory_order_relaxed);
if (prev_val < new_val) {
prev_val = new_val;
break;
}
}
atomic_thread_fence(std::memory_order_acquire);
std::cout << a << '\n';
}
}
このコードは引き続きフェンスを同期させますが、データの競合を排除しません。たとえば、f.load()
たまたま10が返された場合a=1
、、、a=2
...a=10
がすべて発生したことはわかりますcout<<a
が、その特定の前に発生したことはわかりません。これらは、異なるスレッドでの競合する操作であり、発生前の関係はありません。データ競争。cout<<a
a=11