I'm learning about different memory orders.
I have this code, which works and passes GCC's and Clang's thread sanitizers:
#include <atomic>
#include <iostream>
#include <future>
int state = 0;
std::atomic_int a = 0;
void foo(int from, int to)
{
for (int i = 0; i < 10; i++)
{
while (a.load(std::memory_order_acquire) != from) {}
state++;
a.store(to, std::memory_order_release);
}
}
int main()
{
auto x = std::async(std::launch::async, foo, 0, 1);
auto y = std::async(std::launch::async, foo, 1, 0);
}
I reckon that an 'acquire' load is unnecessary if it doesn't end up returning from
, so I decided to use a 'relaxed' load, followed by an 'acquire' fence.
I expected it to work, but it's rejected by the thread sanitizers, which claim that concurrent state++
s are a data race.
#include <atomic>
#include <iostream>
#include <future>
int state = 0;
std::atomic_int a = 0;
void foo(int from, int to)
{
for (int i = 0; i < 10; i++)
{
while (a.load(std::memory_order_relaxed) != from) {}
std::atomic_thread_fence(std::memory_order_acquire);
state++;
a.store(to, std::memory_order_release);
}
}
int main()
{
auto x = std::async(std::launch::async, foo, 0, 1);
auto y = std::async(std::launch::async, foo, 1, 0);
}
Why is this a data race?
Cppreference says that
Atomic-fence synchronization
An atomic release operation X in thread A synchronizes-with an acquire fence F in thread B, if
- there exists an atomic read Y (with any memory order)
- Y reads the value written by X (or by the release sequence headed by X)
- Y is sequenced-before F in thread B
In this case, all non-atomic and relaxed atomic stores that are sequenced-before X in thread A will happen-before all non-atomic and relaxed atomic loads from the same locations made in thread B after F.
In my understanding, all conditions are met:
- "there exists an atomic read Y (with any memory order)" — check:
a.load(std::memory_order_relaxed)
. - "Y reads the value written by X" — check, it reads the value from
a.store(to, std::memory_order_release);
. - "Y is sequenced-before F in thread B" — check.
__tsan_acquire
would also have been nice, but it doesn't seem to be one of their priorities :-( – Dygall