Modern Effective C++ - When to use volatile and when to use atomic.

Unlike volatile in other languages like Java or C#, volatile is not meant to be used in multi-threaded programming in C++. Hence using volatile in C++ multi-threading does not eliminate race conditions. In this article we will discuss more about volatile it's uses and how it's different from atomics.

Usually variables, after their first use in the code, are often stored in register or cache for sometime as the CPU anticipates that it will be used again shortly and fetching from register and cache is much faster than fetching from memory.  But in certain situations, special memory areas are updated from outer sources like a keyboard, network, temperature sensor or another program. Hence these variables associated with special variables, are bound to change in ways which cannot be anticipated by the program or the CPU. These variables are often qualified as volatile.

Volatile guarantees all accesses(both reads and writes) of the volatile variable happens from memory, rather than register or cache. But it provides no guarantees or no lock protection when more than one thread intents to write the same variable. Hence when two threads wants to write a single variable, the answer can be the value that is set by any one of the thread, or a mix of thread1 and thread2 values. Eg take a volatile int. Int is of size 4 bytes. Now consider the below situation.

Thread1 writes 2 bytes. It is then put to waiting state by the scheduler
Thread2 is scheduled to run. Thread2 writes all the 4 bytes of the integer.
Thread1 gets scheduled again. It then writes the remaining two bytes of the integer.

This process is called corrupted writes, and it produces corrupted/junk values.

Now contrast this to atomic. Atomic employs certain machine level instructions to guarantee exclusive access to run atomic supported operations on the atomic variable. Atomic variable is either guaranteed to have value written by Thread1 or Thread2. The final value can never be a mix of values produced by two threads.

Consider the output of the below program


Ideally both x and y should be equal to 100000 after the async tasks complete. But volatile's value i.e y is always less than 100000. The situation happens like this -
The threads T1 and T2, both read the value 0 (for y) from memory, they both increment y, to write back 1 to the memory. So in this case one increment is lost.

But in case of atomics, the CPU orders these increments to happen one after the other. When both T1 and T2 tries to increment the value x, only one thread can increment an atomic variable once(exclusive access). Only after the value has been incremented and saved, is the second thread allowed to proceed which fetches the modified value, increments it and writes back. Hence no increment is lost when using atomic<int>.


Atomic operations are divided into three areas -
  1. Read
  2. Write
  3. Read - Modify - Write


The read operation in the program above is a bit tricky. Although x.load is guaranteed to be atomic, the entire statement is not. There can be some other thread which may change the value of y, after x is loaded , but before x''s value is assigned to y.

We already saw read - modify - write (RMV) in the example above. Atomic read write and read solve the problem of re-ordering as well.

Let's say we have two statements.

x = 3;
y = 2;

Although y is assigned to 2 after x is assigned to 3, in reality since the two statements are not related to each other in anyway, the compiler can choose to reorder it.

Even if the compiler does not re-order it, the modern CPU, in a bid to make programs faster can pipeline multiple instructions and since the statements are independent, in the process of pipelining, the statements can change order.

This does not bode well, when we need to use a certain variable as a flag.


The variables, value and is_value_available might be logically interconnected from the programmer's perspective, but they are totally unrelated (since one variable is not using the value of another variable) from the computer's perspective. Hence it is free to re-order the statements, which might completely break the logic of the intended purpose. We can use atomic variables can solve this issue.



Atomic solves the problem by ensuring no instructions before atomic are reordered with instructions after atomic.

On an end note, it is important to note that -
Volatile - Indicates special memory area  whose values change outside the control of the program. Hence we indicate the compiler to not optimize the memory access to this memory. volatile is a qualifier which can be used in conjunction with any primitive or used defined type. All the operations allowed on any particular type is also allowed  on volatile variable of that type. Not used for concurrent programming.

Atomic - atomic<T> is a user defined template class(created by library creators of C++). It can be used in a lock free manner for simple types like integrals, float and doubles, bools, chars, and pointers only. For other types, a regular mutex is used underneath. Not all operations supported by T, is also supported by atomic<T> . atomic<T> has few special operations (member functions) of it's own like fetch_add. Some member functions are only applicable for a few specific types. Have a look at the program below



Modern Effective C++ - When to use volatile and when to use atomic. Modern Effective C++ - When to use volatile and when to use atomic. Reviewed by zeroingTheDot on May 26, 2018 Rating: 5

No comments:

Powered by Blogger.