My gut feeling is this is a stupid question, as no one has ever asked it. But I am still a bit obsessed with it.
I understand that a std::memory_order_release operation guarantees that all writes prior to it can be seen by another thread when that thread has loaded the atomic variable with std::memory_order_acquire.
My question is: what's the exact definition of "prior to"? Say, in this situation
void write_other()
{
other_var1 = true;
other_var2 = true;
}
void write() // writer thread
{
write_other();
x.store(true, std::memory_order_relaxed);
y.store(true, std::memory_order_release); // release!!
}
void read() // reader thread
{
while (!y.load(std::memory_order_acquire)) {} // acquire!!
assert(x.load(std::memory_order_relaxed)); // this is definitely true
assert(other_var1.load(std::memory_order_relaxed)); // is this true?
assert(other_var2.load(std::memory_order_relaxed)); // is this true?
}
Then in the main thread, I launch the two threads.
std::vector<std::thread> threads;
threads.push_back(std::thread(&read));
threads.push_back(std::thread(&write));
In the writer thread, the write to other_var1 and other_var2 happen in function "write_other" that is called by the thread entry function "write". In this situation, can the updates to other_var1 and other_var2 be synchronized to the loading of y in the reader thread?
In other words, what's the scope of the relative order guarantee? Should it cover only the current code block? Or should it cover all updates that have ever happened in the writer thread before the std::memory_order_release operation?
CodePudding user response:
If some line L1 is written before another line L2 then L1 is sequenced before L2. That means that L1 fully executes before L2. If L1 is sequenced before L2 and L2 synchronizes with L3 in another thread (T2) then L1 happens before L3. And if something happens before another then it is also fully executed before it.
Now you can use it in your example: write_other();
- L1, y.store(true, std::memory_order_release);
- L2, while (!y.load(std::memory_order_acquire)) {}
- L3. So all your asserts will be true.
CodePudding user response:
Yes, the effect of release
covers everything thread 1 did before, not limited to the code block.
It can even cover things done by different threads, if they had synchronized with thread 1 before.
what's the exact definition of "prior to"?
The standard calls this happens-before. It has a rather obscure definition, but fortunately, you don't need to care about it.
In absense of consume
operations (the use of which is discouraged by the standard, since apparently no modern compiler supports them, silently replacing them with stronger acquire
operations), this is equivalent to a lot less obscure simply-happens-before.
A simply-happens-before B if:
- A is sequenced-before B (i.e. they're both in the same thread, and one runs before the other), or
- A synchronizes-with B (most often A is a release operation, and B is an acquire operation in a different thread that reads the value from A; but synchronization also happens in some other scenarios).
- This relation is transitive, meaning that any number of chained "sequenced-before" and "synchronizes-with" also imposes "simply-happens-before".
There's also strongly-happens-before, which is same as simply-happens-before in absense of mixing release/acquire with seq-cst on the same variable (read more).