The answer is yes and perhaps no
The memory model principles:
C++11 atomics use by default the std::memory_order_seq_cst
memory ordering, which means that operations are sequentially consistent.
The semantics of this is that ordering of all operations are as if all these operations were performed sequentially :
C++ standard section 29.3/3 explains how this works for atomics: "There shall be a single total order S on all memory_order_seq_cst operations, consistent with the “happens before” order and modification orders for all affected locations, such that each memory_order_seq_cst
operation that loads a value observes either the last preceding modification according to this order S, or the result of an operation that is not memory_order_seq_cst."
The section 1.10/5 explains how this impacts also non-atomics: "The library defines a number of atomic operations (...) that are specially identified as synchronization operations. These operations play a special role in making assignments in one thread visible to another."
The answer to your question is yes !
Risk with non-atomic data
You shall however be aware that in reality the consistency guarantee is more limited for the non-atomic values.
Suppose a first execution scenario:
(thread 1) A.foo = 10;
(thread 1) A.foo = 4; //stores an int
(thread 1) ptr.store(&A); //ptr is set AND synchronisation
(thread 2) int i = *ptr; //ptr value is safely accessed (still &A) AND synchronisation
Here, i
is 4. Because ptr
is atomic, thread (2) safely gets the value &A
when it reads the pointer. The memory ordering ensures that all assignments made BEFORE ptr
are seen by the other threads ("happens before" constraint).
But suppose a second execution scenario:
(thread 1) A.foo = 4; //stores an int
(thread 1) ptr.store(&A); //ptr is set AND synchronisation
(thread 1) A.foo = 8; // stores int but NO SYNCHRONISATION !!
(thread 2) int i = *ptr; //ptr value is safely accessed (still &A) AND synchronisation
Here the result is undefined. It could be 4 because of the memory ordering guaranteed that what happens before the ptr
assignement is seen by the other threads. But nothing prevents assignments made afterwards to be seen as well. So it could be 8.
If you would have had *ptr = 8;
instead of A.foo=8;
then you would have certainty again: i
would be 8.
You can verify this experimentally with this for example:
void f1() { // to be launched in a thread
secret = 50;
ptr = &secret;
secret = 777;
this_thread::yield();
}
void f2() { // to be launched in a second thread
this_thread::sleep_for(chrono::seconds(2));
int i = *ptr;
cout << "Value is " << i << endl;
}
Conclusions
To conclude, the answer to your question is yes, but only if no other change to the non atomic data happens after the synchronisation. The main risk is that only ptr
is atomic. But this does not apply to the values pointed to.
To be noted that especially pointers bring further synchronisation risk when you reassign the atomic pointer to a non atomic pointer.
Example:
// Thread (1):
std::atomic<Object*> ptr;
A.foo = 4; //foo is an int;
ptr.store(*A);
// Thread (2):
Object *x;
x=ptr; // ptr is atomic but x not !
terrible_function(ptr); // ptr is atomic, but the pointer argument for the function is not !
ptr.store(&A)
?ptr.store(*A)
makes no sense (unlessObject
definesObject * Object::operator*();
...). – Gape