c++multithreadingc++11c++14relaxed-atomics

memory_order_relaxed with c++11/14 in cppreference


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?


Solution

  • 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.