How it works...

In this recipe, we will learn how to use C++'s atomic data types. Atomic data types are limited to simple data types such as integers, and since these data types are extremely complicated to implement, the only operations that are supported are simple operations such as add, subtract, increment, and decrement.

Let's take a look at a simple example that not only demonstrates how to use an atomic data type in C++, but also demonstrates why atomic data types are so important:

#include <atomic>
#include <thread>
#include <iostream>

int count{};
std::atomic<int> atomic_count{};

void foo()
{
do {
count++;
atomic_count++;
}
while (atomic_count < 99999);
}

int main(void)
{
std::thread t1{foo};
std::thread t2{foo};

t1.join();
t2.join();

std::cout << "count: " << count << ' ';
std::cout << "atomic count: " << atomic_count << ' ';

return 0;
}

When this code is executed, we get the following:

In the preceding example, we have two integers. The first integer is a normal C/C++ integer type, while the second is an atomic data type (of type integer). We then define a function that loops until the atomic data type is 1000. Finally, we execute this function from two threads, which means our global integers are incremented by two threads simultaneously.

As you can see, the output of this simple test shows that the simple C/C++ integer data type is not the same value as the atomic data type, yet both are incremented the same number of times. The reason for this can be seen in the assembly of this function (on an Intel CPU), as follows:

To increment an integer (without optimizations enabled), the compiler must move the contents of memory into a register, add 1 to the register, and then write the results of the register back to memory. Since this code is executing simultaneously in two different threads, this code interleaves, resulting in corruption. The atomic data type does not suffer this same problem. This is because the process of incrementing the atomic data type occurs in a single, special instruction that the CPU ensures to execute, without interleaving its internal state with the same internal state of other instructions, on other CPUs.

Atomic data types are typically used to implement synchronization primitives such as std::mutex (although, in practice, std::mutex is implemented using test and set instructions, which use a similar principle but oftentimes execute faster than atomic instructions). These data types can also be used to implement special data structures called lock-free data structures, which are capable of operating in multithreaded environments without the need for std::mutex. The benefit of lockless data structures is that there are no wait states when dealing with thread synchronization at the expense of more complicated CPU hardware and other types of performance penalties (most CPU optimizations provided by the hardware have to be temporarily disabled when the CPU encounters an atomic instruction). So, like anything in computer science, they have their time and place.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset