Consider this example:
#include <atomic>
#include <iostream>
#include <chrono>
#include <thread>
#include <cassert>
int main(){
std::atomic<int> val = {0};
auto t1 = std::thread([&]() {
auto r = val.load(std::memory_order::relaxed); // #1
assert(r==1);
});
auto t2 = std::thread([&]() {
std::this_thread::sleep_for(std::chrono::milliseconds(6000)); // #2
val.store(1,std::memory_order::relaxed); // #4
});
t1.join();
auto now1 = std::chrono::high_resolution_clock::now();
std::cout<<"t1 completed\n";
t2.join();
auto now2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = now2 - now1;
// duration >=6
}
Is it possible to observe that #1 loads 1 and "t1 completed" is immediately printed(i.e., t1 completes quickly), #4 is executed by t2 six seconds after #2, in terms of the abstract machine's perspective? In other words, #1 reads a value that hasn't been produced yet by t2 at that point.
[intro.races] p10 says:
The value of an atomic object M, as determined by evaluation B, is the value stored by some unspecified side effect A that modifies M, where B does not happen before A.
From the perspective of memory order, #1 and #4 are unordered(in terms of happen-before), so #4 is a possible value read by #1. However, from the perspective of execution, #4 is executed by thread t2 after at least six seconds. That is, #4 hasn't been executed by thread t2 yet at the point when t1 was completed. Although the abstract machine doesn't care about the timeline, it only cares about ordering; however, under the described outcome, #1 reads a value that has not been produced by t2 when t1 is completed. So, I wonder, is the described outcome a conforming observable behavior in the pure abstract machine's perspective?
t1doesn't perform any observable side effects at all, so in principle, the whole thread execution can be optimized away under the as-if rule. You are asking for an answer in terms of the abstract machine, but you aren't formulating your question in terms meaningful for the abstract machine.std::coutare synchronized with each other. Ifstd::cout<< ris printed first, then it can only print0since in this caseval.loadhappens-beforeval.store. Ifstd::cout<< ris printed second (unlikely in practice because of the delay, but theoretically possible) then the load and the store are unsynchronized and either0or1can be printed.std::coutintroduces the wrong intention for the original example. I updated the example.