问题
Suppose I have 2 threads:
int value = 0;
std::atomic<bool> ready = false;
thread 1:
value = 1
ready = true;
thread 2:
while (!ready);
std::cout << value;
Is this program able to output 0?
I read about the C++ memory model - specifically, sequential consistency, which I believe is the default, and it wasn't particularly clear. Is the compiler only required to put atomic operations in the correct order relative to each other, or is it required to put atomic operations in the right order relative to all other operations?
回答1:
By default operations on atomic variables are done using the memory_order_seq_cst
semantics, which guarantees that no reordering will be done.
Thus the line: value = 1
cannot be reordered below the atomic assignment: value = 1
, so the line std::cout << value;
will always print 1.
By the same rules, the line: std::cout << value;
cannot be reordered
above the line: while (!ready);
.
回答2:
It acts like a memory barrier, as per ShadowRanger's response. However, for more details on why it does that, I suggest looking at Herb Sutter's talk on atomic weapons. He goes into great detail about how and why atomics work.
回答3:
Is this program able to output 0?
No.
Your reasoning is correct. In ISO C++ seq_cst and acq_rel atomics can create happens-before / after relationships between threads, making it safe for one thread to write a non-atomic variable and then another thread to read it without data-race UB.
And you've correctly done so in this case: the spin-wait loop is a seq-cst load from the flag that only exits the loop upon seeing a true
value. The evaluation of non-atomic value
happens-after the load that sees the true
. And in the writer, the sequential-release store makes sure that the flag store is not seen before the value store.
When compiling to asm for a normal ISA, compilers have to respect ordering of non-atomic stores before release-stores, and of non-atomic loads after acquire loads. Unless it can somehow prove that there would still be UB for any other possible thread observing this.
回答4:
Yes, but only because you used a default.
Cst suffers because it uses a global scope for re-ordering; which is an artifact of old architectures. The newer architectures have a more granular scope for sequencing, so you might expect a standards update will invalidate your code in the near future.
Write queues can end up with many dozens of entries each with an egregious bus latency to resolution. Partitioning these into the ones that matter vs those that don't is an obvious step, one that new architectures already embody.
The C++ standards creation committee are clearly out of their depth, and should stop inventing useless crap.
回答5:
First note: it's absolutely impossible to reason about any C or C++ program in any version of C and C++ that supports thread, because of the possibility of UB (undefined behavior), because there is no well defined abstract thread semantics, or any semantic at all defined. This is yet another major theoretical as well as practical flaw in C and C++ semantics (on top of many other crippling flaws).
But you can reason in practical terms: compilers are very predictable in their implementation of threading primitives (that may not be the case in the future as compiler writers get fluent in thread semantics and begin breaking things using claims of UB).
When using thread communication primitives the compiler does the right thing to guarantee flow of information. while (!ready);
guarantees that a thread exits the loop after the atomic objects is set: there is a well defined "past".
As a practical real world example of an ill defined "past", remember the Apollo audio exchanges with astronauts talking over Houston: there was no well defined notion of who began talking first as the astronauts were very far away, and the only recording we have (from Houston) shows an order but an hypothetical recording from the spaceship would show another, and neither order is the correct one. Astronauts and Houston began talking without order, neither were in the past of the others. Until you see that, you can't claim to understand relativity.
With multithreading, you can have memory operations that are not in a the past of others, and can't know what they will observe if they attempt to use objects manipulated in the non-past.
来源:https://stackoverflow.com/questions/40320254/reordering-atomic-operations-in-c