With the new C++11 Standard, C++ faces the first time the challenges of multicore architectures. The 2011 published standard defines how a C++ program has to behave in the presence of multiple threads. The C++11 multithreading capabilities are composed of two components. This is on the one hand, the defined memory model, which is on the other hand, the standardized threading interface.
A well defined memory model
The defined memory model is the necessary basis so that multithreaded programming make sense in C++. Thus, the memory model has to give answers to the following questions.
- What are atomic operations?
- Which order of operations is ensured?
- When are memory effects of operations visible?
To 1:Atomic operations are operations that follow the first three letters of the famous ACID Idioms from the database theory. Atomic operations are atomic (A), going from one consistent (C) state to the next and are executed in isolation (I). This means in particular, no other thread can observe an intermediate state of an atomic operation. The incrementation atomVar++ shows the consistency and isolation of an atomic operation very nice. If atomVar is an atomic variable, atomVar can have only the old or the new value. The consistency of the variable atomVar is, that it changes only from one state to the other and the isolation, that another thread can not observe any intermediate value.
To 2:Both the compiler that translate the program into assembler instructions, and the processor that executes the assembler instructions, can rearrange the operations. Most often this is for performance reasons. In addtion the various tiers of storage (cache) posse the possibility to provide the result of the operations in a delayed way.
To 3:Since it is quite possible that one thread sees an operation on a variable later than another, the threads have to obey certain rules.
The standardized threading interface
The standardized threading interface in C++11 is composed of the following components.
- Thread local data
- Condition variables
To 1:Threads are the basic building blocks of multithreaded programming. They do their work autonomously, are parameterized by arguments and interact with other threads via shared variables.
To 2:Tasks are a relatively modern concept. Tasks consist of two components, which are connected by a communication channel. One component as endpoint of the channel produces the result, while the other endpoint consumes it. The producer is called Promise, the consumer Future.
To 3: Thread local data is data – such as it is easy to guess from the name- that explicitly belongs to one thread.
To 4:Condition variables enables it to implement producer/consumer workflows. The consumer waits for the notification of the producer so that he can continue his work.
What will come with C++17 and C++20?
The next C++ standards are planned for 2017 and 2020. C++17 and C++20 will consist on many extensions around the multithreading capabilities of the existing standard. Because the existing functionality is very basic. These changes will likely include the following three interesting features:
- Latches and barriers
- Transactional memory
- Automatically parallelizing or vectorizing algorithms of the Standard Template Library (STL)
To 1: Latches and barriers are similar to semaphores .
To 2:Transactional memory is in simple words the idea of ACID applied (again only the first three letters) to code. That means, the code is annotated as transactional memory and the code is optimistically excuted without synchronization with other threads. At the end of the transaction the results will only be published, if the initial conditions are still valid. If not, the outcome of the result is rejected and the transation is again executed. While the critial area is always locked by mutexes, the transaction is not locked, but possibly the result will be discarded. A critical area is a section of code, that at most one thread is allowed to enter at a time.
To 3: While parallelizing algorithms distribute the operations on their containers on multiple threads, vectorizing algorithms preform their operations on several elements of its container in a single step.
In the next few articles I will look deeper into the components of the C++ memory model and the standardized threading interface. My focus is not to elaborate on every detail. The details are very well document in the current C++ standard 14882:2014 or in the webpage cppreference.com.
My focus will particularly be in the next few articles to show you typical errors in dealing with multithreaded programs and of course suggest solutions. For this purpose I will incorporate as much theory to understand the problem and the solution as necessary. I start with the standardized threading interface.
In the next article I deal with the creation of threads.