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.