I'm confused with this statement on Relaxed ordering
Even with relaxed memory model, out-of-thin-air values are not allowed to circularly depend on their own computations, for example, with x and y initially zero,
// Thread 1: r1 = y.load(std::memory_order_relaxed); if (r1 == 42) x.store(r1, std::memory_order_relaxed); // Thread 2: r2 = x.load(std::memory_order_relaxed); if (r2 == 42) y.store(42, std::memory_order_relaxed);
is not allowed to produce r1 == r2 == 42 since the store of 42 to y is only possible if the store to x stores 42, which circularly depends on the store to y storing 42. Note that until C++14, this was technically allowed by the specification, but not recommended for implementors.
I've done a test with the following code, running for about 2 hours, neither r1 == 42 nor r2 == 42 occurs.
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> x(0);
std::atomic<int> y(0);
int r1 = 0;
int r2 = 0;
void foo1() {
r1 = y.load(std::memory_order_relaxed);
if (r1 == 42) {
x.store(r1, std::memory_order_relaxed);
}
}
void foo2() {
r2 = x.load(std::memory_order_relaxed);
if (r2 == 42) {
y.store(42, std::memory_order_relaxed);
}
}
int main() {
while (1) {
std::thread t1(foo1);
std::thread t2(foo2);
t1.join();
t2.join();
if (r1 == 42 || r2 == 42) {
break;
}
}
std::cout << "r1 " << r1 << ", r2 " << r2 << std::endl;
return 0;
}
OS: Ubuntu 22.04.03 LTS (WSL2)
G++: 11.4.0
Compile command: g++ -std=c++11 test.cpp -pthread
My questions are: What does 'Note that until C++14, this was technically allowed by the specification, but not recommended for implementors.' means? Does it means to produce r1 == r2 == 42 with this code is possible with C++11? If so, why is this possible?
Does it means to produce r1 == r2 == 42 with this code is possible with C++11? If so, why is this possible?
Yes, it is technically allowed. It is possible, because it is allowed. Whether any hardware/implementation would actually be able to produce that outcome is a different question than what the language specification allows.
This is however more of an issue with the specification rather than intended possible behavior. I do not expect that any hardware should actually be able to produce such outcomes.
See e.g. N3710 for a discussion of why it is difficult to write the specification in such a way that out-of-thin-air values are impossible (and N3786 for the reduced wording that made it into C++14).
Cppreference seems to be wrong btw. In C++11 there was an attempt at specification to prohibit out-of-thin-air values, which according to the paper linked above made relaxed atomics practically unimplementable. C++14 replaces that specification with a recommendation to avoid out-of-thin-air values without actually specifying what that means exactly.
A recommendation is just that and a C++ implementation does not need to follow it to be conforming, although it would certainly be against the intent to not follow it.