Are fundamental C / C ++ types atomic?

They are fundamental types of C / C ++, such as int , double , etc., atomic, for example. thread safe?

They are free of data races; that is, if one thread writes an object of this type while another thread reads from it, is the behavior correct?

If not, does it depend on the compiler or something else?

+57
c ++ c multithreading atomic
Feb 05 '16 at 2:04 on
source share
4 answers

No, basic data types (e.g. int , double ) are not atomic, see std::atomic .

Instead, you can use std::atomic<int> or std::atomic<double> .

Note: std::atomic was introduced with C ++ 11, and I understand that before C ++ 11, the C ++ standard did not recognize the existence of multithreading at all.




As @Josh pointed out, std::atomic_flag is an atomic Boolean type. guaranteed not to block , unlike specialized std::atomic .




Quote from the document: http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf . I am sure that the standard is not free, and therefore it is not the final / official version.

1.10 Multithreaded executions and data schedules

  1. Two evaluations of expressions contradict each other if one of them changes the memory location (1.7) and the other reads or changes the same memory location.
  2. The library defines a series of atomic operations (section 29) and operations on mutexes (article 30), which are specifically identified as synchronization operations. These operations play a special role in assigning appointments in one thread that is visible to another. The synchronization operation in one or more memory cells is either a consumption operation, a receive operation, a release operation, or a receive and release operation. The synchronization operation without an attached memory cell is a fence and can be either a fence fence, a fence gap, or both a fence and a release fence. In addition, there are relaxed atomic operations, which are not synchronization operations, and atomic read-modify-write operations, which have special characteristics.


  1. Two actions are potentially parallel if
    (23.1) - they are executed by different threads or
    (23.2) - they do not have a sequence, and at least one is executed by a signal handler.
    A program execution contains a data race if it contains two potentially parallel conflicting actions, at least one of which is not atomic, and does not occur before the other, with the exception of a special case for signal handlers described below. Any such data race results in undefined behavior.

29.5 Atomic types

  1. There must be explicit atomic pattern specializations for the integral types `` char, signed char , unsigned char , short , unsigned short , int , unsigned int , long , unsigned long , long long , unsigned long long , char16_ t, char32_t , wchar_t and any others types needed for typedefs in the <cstdint> header. For each integral type integral, the atomic<integral> specialization provides additional atomic operations corresponding to integral types. There should be a specialization atomic<bool> , which provides the general atomic operations specified in 29.6.1.


  1. There should be partial specializations of the atomic class template pointer. These specializations must have a standard layout, trivial default constructors, and trivial destructors. Each of them must support syntax syntax.

29.7 Flag Type and Operations

  1. Operations on an object of type atomic_flag must be locked. [Note. Consequently, operations must also be without addresses. No other type requires locking, so the atom_flag type is the smallest hardware-implemented type necessary to comply with this international standard. The remaining types can be emulated using atom_flag, but with less ideal properties. - final note]
+67
Feb 05 '16 at 14:08
source share

Since C is also (currently) mentioned in the question, even though it is not in the tags, the C> Standard indicates:

5.1.2.3 program execution

...

When processing an abstract machine is interrupted upon receipt of a signal, the values โ€‹โ€‹of objects that are not atom-blocking objects or the volatile sig_atomic_t type are not specified, as is the state of the floating-point environment. The value of any object changed by the handler, which is neither a lock-free atomic object, nor the volatile sig_atomic_t type becomes undefined when the handler exits, like the state of a floating point environment, if it is changed by the handler and not restored to its original state.

and

5.1.2.4 Multithreaded executions and data schedules

...

Two evaluations of expressions if one of them changes the location of the memory, and the other reads or changes the same place in the memory.

[several pages of standards - some paragraphs explicitly addressing atomic types]

Program execution contains when it contains two conflicting actions in different threads, at least one of which is not atomic, and does not occur before the other. Any such data race results in undefined behavior.

Note that values โ€‹โ€‹are โ€œundefinedโ€ if the signal interrupts processing and simultaneous access to types that are clearly not atomic is undefined behavior.

+15
Feb 05 '16 at 14:23
source share

What is atomic?

Atomic, as describing something with the property of an atom. The word "atom" comes from the Latin atomus , which means "undivided".

I usually think that an atomic operation (regardless of language) has two qualities:

Atomic operation is always inseparable.

those. it is executed in an inseparable way, I believe that this is what the OP stands for "thread safe". In a sense, the operation occurs instantly when viewed by another stream.

For example, the following operation will most likely be split up (depends on the compiler / hardware):

 i += 1; 

since it can be seen by another thread (on hypothetical hardware and the compiler) as:

 load r1, i; addi r1, #1; store i, r1; 

Two threads performing the above operation i += 1 without proper synchronization can lead to an incorrect result. Say i=0 , thread T1 loads T1.r1 = 0 , and thread T2 loads t2.r1 = 0 . Both threads increase their respective r1 by 1, and then save the result to i . Although two increments have been performed, the value of i is still only 1, because the increment operation is divisible. Note that if there was synchronization before and after i+=1 , then another thread would wait until the operation was completed, and thus would observe the whole operation.

Note that even a simple entry may or may not be indivisible:

 i = 3; store i, #3; 

depending on the compiler and hardware. For example, if the address i not properly coordinated, then it is necessary to use a non-standard load / storage, which is performed by the CPU as several smaller loads / storages.

Atomic operation has guaranteed semantics of memory ordering.

Non-atomic operations may be reordered and may not necessarily be performed in the order specified in the source code of the program.

For example, in the "as-if" rule, the compiler is allowed to reorder stores and load as he sees fit as long as all access to volatile memory occurs in the order specified by the program as if the program was evaluated in accordance with the wording in the standard . Thus, non-atomic operations can be reordered, violating any assumptions about the execution order in a multi-threaded program. This is why the apparently innocent use of the raw int as a signal variable in multi-threaded programming is violated, even if the writes and reads can be indivisible, ordering can disrupt the program depending on the compiler. The atomic operation provides an ordering of operations around it depending on what semantics of memory are indicated. See std::memory_order .

The CPU can also reorder your memory accesses according to the memory limits for that CPU. You can find memory order limits for the x86 architecture in the Intel 64 and IA32 Architecture Software Developer's Guide starting on page 2212.

Primitive types ( int , char , etc.) are not Atomic

Because even if under certain conditions they may have indivisible instructions for storage and loading, or perhaps even some arithmetic instructions, they do not guarantee the storage and loading order. Therefore, they are unsafe for use in multi-threaded contexts without proper synchronization to ensure that the state of memory observed by other threads is what you think is at the moment.

Hopefully this explains why primitive types are not atomic.

+8
Feb 05 '16 at 22:51
source share

Additional info that I haven't seen in other answers yet:

If you use std::atomic<bool> , for example, and bool is actually atomic in the target architecture, then the compiler will not generate extra fences or locks. The same code will be generated as a normal bool .

In other words, using std::atomic makes the code less efficient if it is really necessary for correctness on the platform. Therefore, there is no reason to avoid this.

+3
Feb 05 '16 at 23:45
source share



All Articles