is assignment operator '=' atomic?
This code is not guaranteed to be thread-safe on Win32, since Win32 guarantees atomicity only for properly-aligned 4-byte and pointer-sized values. bool
is not guaranteed to be one of those types. (It is typically a 1-byte type.)
For those who demand an actual example of how this could fail:
Suppose that bool
is a 1-byte type. Suppose also that your is_true
variable happens to be stored adjacent to another bool
variable (let's call it other_bool
), so that both of them share the same 4-byte line. For concreteness, let's say that is_true
is at address 0x1000 and other_bool
is at address 0x1001. Suppose that both values are initially false
, and one thread decides to update is_true
at the same time another thread tries to update other_bool
. The following sequence of operations can occur:
- Thread 1 prepares to set
is_true
totrue
by loading the 4-byte value containingis_true
andother_bool
. Thread 1 reads 0x00000000. - Thread 2 prepares to set
other_bool
totrue
by loading the 4-byte value containingis_true
andother_bool
. Thread 2 reads 0x00000000. - Thread 1 updates the byte in the 4-byte value corresponding to
is_true
, producing 0x00000001. - Thread 2 updates the byte in the 4-byte value corresponding to
other_bool
, producing 0x00000100. - Thread 1 stores the updated value to memory.
is_true
is nowtrue
andother_bool
is nowfalse
. - Thread 2 stores the updated value to memory.
is_true
is nowfalse
andother_bool
is nowtrue
.
Observe that at the end this sequence, the update to is_true
was lost, because it was overwritten by thread 2, which captured an old value of is_true
.
It so happens that x86 is very forgiving of this type of error because it supports byte-granular updates and has a very tight memory model. Other Win32 processors are not as forgiving. RISC chips, for example, often do not support byte-granular updates, and even if they do, they usually have very weak memory models.
Atomic Assignment Operator
if mValue
is a primitive type (and at most 32 bits wide on a 32-bit CPU, at most 64 bits wide on a 64-bit CPU), and you're running on an x86 CPU (in 32 or 64 bit mode) and you don't manually misalign data, then memory reads/writes are guaranteed to be atomic.
This does not, in itself, mean that the compiler won't reorder memory accesses or even optimize it out entirely, but the CPU does guarantee that any well-aligned read/write with data of those sizes will be atomic.
However, note that I'm talking about atomicity, not thread safety, because "thread safety" depends on the context in which the code is used.
Atomicity of the simple assignment operator
Following the example in this Dr Dobbs article, simple assignment of atomic variables in C11 is atomic.
The C11 standard (ISO/IEC 9899:2011), section 6.2.6.1/9 reads:
Loads and stores of objects with atomic types are done with
memory_order_seq_cst
semantics.
In addition to being atomic, operations performed with memory_order_seq_cst
semantics have a single ordering observed by all threads (aka sequentially-consistent ordering).
Without the _Atomic
type qualifier, it is possible for an assignment to be non-atomic. Assigning a 64 bit value (e.g. a long long
) on a 32 bit machine requires two CPU cycles. If another thread reads the value between those two cycles they'll get 4 bytes of the old value and 4 bytes of the new value.
Openmp atomic and critical
According to the docs here you can only use atomic with certain statement forms:
Also, make sure the comparison is inside the critsec! So I assume you cannot have what you want, but if you had
if(prod > final_prod) // unsynchronized read
{
#pragma omp critical
final_prod = prod;
}
it would still be data race
Use of deleted function error with std::atomic_int
Your code is attempting to construct a temporary std::atomic_int
on the RHS, then use the std::atomic_int
copy constructor (which is delete
d) to initialise stop
, like so:
std::atomic_int stop = std::atomic_int(0);
That's because copy-initialisation, as you are performing here, is not quite equivalent to other kinds of initialisation.
[C++11: 8.5/16]:
The semantics of initializers are as follows [..]If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).
(this allows for option 3, at the end of this answer)
[..]
If the destination type is a (possibly cv-qualified) class type:
- If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
(this almost describes your code but not quite; the key here is that, perhaps contrary to intuition, std::atomic_int
's constructors are not considered at all in your case!)
- Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3). If the conversion cannot be done or is ambiguous, the initialization is ill-formed. The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the cv-unqualified version of the destination type. The temporary is a prvalue. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.
(this is your scenario; so, although the copy can be elided, it still must be possible)
- [..]
Here's the fix, anyway; use direct-initialisation or list-initialisation:
std::atomic_int stop(0); // option 1
std::atomic_int stop{0}; // option 2
std::atomic_int stop = {0}; // option 3
Why is reference assignment atomic in Java?
First of all, reference assignment is atomic because the specification says so. Besides that, there is no obstacle for JVM implementors to fulfill this constraint, as 64 Bit references are usually only used on 64 Bit architectures, where atomic 64 Bit assignment comes for free.
Your main confusion stems from the assumption that the additional “Atomic References” feature means exactly that, due to its name. But the AtomicReference
class offers far more, as it encapsulates a volatile
reference, which has stronger memory visibility guarantees in a multi-threaded execution.
Having an atomic reference update does not necessarily imply that a thread reading the reference will also see consistent values regarding the fields of the object reachable through that reference. All it guarantees is that you will read either the null
reference or a valid reference to an existing object that actually was stored by some thread. If you want more guarantees, you need constructs like synchronization, volatile
references, or an AtomicReference
.
AtomicReference
also offers atomic update operations like compareAndSet
or getAndSet
. These are not possible with ordinary reference variables using built-in language constructs (but only with special classes like AtomicReferenceFieldUpdater
or VarHandle
).
Related Topics
Why Do C++ Streams Use Char Instead of Unsigned Char
How to Typedef a Template Class
What Is the Right Approach When Using Stl Container for Median Calculation
Sort Based on Multiple Things in C++
How to Use Memcpy in C++ to Copy Classes That Have No Pointers or Virtual Functions
How to Erase & Delete Pointers to Objects Stored in a Vector
Getchar_Unlocked( ) VS Scanf() VS Cin
How to Create and Initialize an Array of Values Using Template Metaprogramming
How to Find the Name of the Calling Function
Are Ieee Floats Valid Key Types for Std::Map and Std::Set
What Is the Performance Impact of Using Int64_T Instead of Int32_T on 32-Bit Systems
What's Time Complexity of This Algorithm for Finding All Combinations
Virtual Dispatch Implementation Details
Can Undefined Behavior Erase the Hard Drive