45
edits
Changes
→Asynchronous Multi-Threading
===What are C++ 11 Threads===
With the introduction of C++ 11, there were major changes and additions made to the C++ Standard libraries. One of the most significant changes was the inclusion of multi-threading libraries. Before C++ 11 in order to implement multi-threading, external libraries or language extensions such as OpenMp was required. Not only the standard library now include support for multi-threading, it also offered synchronization and thread safety.
The C++ 11 thread support library includes these 4 files to enable multi-threading
* <contition_variable> - a synchronization primitive that can be used to block a thread, or multiple threads at the same time, until another thread both modifies a shared variable (the condition), and notifies the condition_variable.
* <future> - Describes components that a C++ program can use to retrieve in one thread the result (value or exception) from a function that has run in the same thread or another thread.
Two options are available for multi-threading. Synchronous threading via std::thread and Asynchronous threading via std::async and std::future.
===Creating and executing Threads===
====OpenMp====
Inside a declared OpenMp parallel region, if not specified via an environment variable OMP_NUM_THREADS or the library routine omp_get_thread_num() , OpenMp will automatically decide how many threads are needed to execute parallel code.
An issue with this approach is that OpenMp is unaware how many threads a CPU can support. A result of this can be OpenMp creating 4 threads for a single core processor which may result in a degradation of performance.
Automatic thread creation
}
====C++ 11====
C++ 11 Threads on the contrary always required to specify the number of threads required for a parallel region. If not specified by user input or hard-coding, the number of threads supported by a CPU can also be accurately via the std::thread::hardware_concurrency(); function.
OpenMp automatically decides what order threads will execute. C++ 11 Threads require the developer to specify in what order threads will execute. This is typically done within a for loop block. Threads are created by initializing the std::thread class and specifying a function or any other callable object within the constructor.
int numThreads = std::thread::hardware_concurrency();
std::vector<std::thread> threads(numThreads);
for (int ID = 0; ID < numThreads; ID++) {
threads[ID] = std::thread(function);
} After the initial creation and execution of a thread, the main thread must either detach or join the thread. The C++ 11 standard library offers these two member functions for attaching or detaching threads. * std::thread::join - allows the thread to execute in the background independently from the main thread. The thread will continue execution without blocking nor synchronizing in any way and terminate without relying on the main thread.* std::thread::detach - waits for the thread to finish execution. Once a thread is created another thread can wait for the thread to finish. Each created thread can then be synchronized with the main thread for (int i = 0; i < threads.size(); i++){ threads.at(i).join(); } ===Parallelizing for Loops=== In OpenMp, paralleling for loops can be accomplished using SPMD or Work-Sharing. When using work-sharing, the omp for construct makes parallelizing for loops a straight-forward and simple process.By placing the appropriate #pragma omp construct over the loop to be parallelized, the range for distributing work across multiple threads is automatically calculated by OpenMp. All that is required to use the omp for construct is to remove any possible data-dependencies within the parallel region. <br>C++ 11 threads and language native threads unfortunately lack this luxury. In order to parallelize a loop using std Threads, it is the programmers responsibility to calculate the range of each iteration within the loop the be parallelized. This is usually done using SPMD techniques. ===Synchronization=== C++ 11 and Openmp are designed to avoid race conditions and share data between threads in various ways. ====Shared Memory==== =====OpenMp===== Openmp uses the shared clause to define what variables are shared among all threads. All threads within a team access the same storage area for shared variables. The he shared clause would be located within a pragma statement. The clause is defined as follows. shared(var) =====C++ 11=====The atomic class provides an atomic object type which can eliminate the possibility of data races by providing synchronization between threads. Accesses to atomic objects may establish inter-thread synchronization and order non-atomic memory accesses. <br>Atomic types are defined as std::atomic<type> var_name; ====Mutual Exclusion=========OpenMp=====Openmp offers multiple solutions for handling mutual exclusion. Scoped Locking may be implemented using the omp_set_lock and omp_unset_lock template functions to allow thread blocking. Example of Scoped Locking omp_lock_t lock; omp_init_lock(&lock); int i = 0; #pragma omp parallel num_threads(8) { omp_set_lock(&lock); i++; omp_unset_lock(&lock); } omp_destroy_lock(&lock); A lock is somewhat similar to a critical section as it guarantees that some instructions can only be performed by one process at a time. With a lock you make sure that some data elements can only be touched by one process at a time. Openmp offers a easier solution for mutual exclusion and preventing race conditions within its section constructs as the programmer does not have to worry about initializing and destroying locks. * critical - region to be executed by only one thread at a time* atomic - the memory location to be updated by one thread at a time A critical section works by acquiring a lock, which carries a substantial overhead. Furthermore. If a thread is in one critical section, the other ones are all blocked. A critical region can by implemented as follows #pragma omp critical { i++; }
A atomic region is implemented just as critical region, only the critical construct is replaced by an atomic construct. An atomic section has much lower overhead then a critical section as it does not require locking and unlocking operations as it takes advantage of the hardware providing atomic increment operations.
===Programming Models==C++ 11=====SPMD====
Example of thread locking/blocking
#include <iostream>
#include <thread>
#include <atomicstring> using namespace std::chrono;#include <mutex>
std::atomic<double> pimutex mu;
void reportTimeshared_output(const char* std::string msg, steady_clock::duration spanint id) { auto ms = duration_cast<milliseconds>mu.lock(span); std::cout << msg << " - took - :" <<id << std::endl; msmu.countunlock(); } void thread_function() { for (int i = -1000; i << 0; i++) shared_output(" millisecondsthread " << , i); } int main() { std::endlthread t(&thread_function); for (int i = 1000; i > 0; i--) shared_output("main thread", i); t.join(); return 0;
}
===Implementations=== Serial Implementation void run#include <iostream> #include <chrono> using namespace std::chrono; int main(int IDargc, double stepSize, int nthrds, int nchar *argv[])
{
}
}
The example finished execution at 180 milliseconds
OpenMp with work-sharing implementation. Since the program is adding data, a reduction pattern can be used with Openmp's work-sharing constructs.
#include <iostream>
#include <chrono>
#include <omp.h>
using namespace std::chrono;
int main(int argc, char *argv[])
{
const size_t n = 100000000;
}
Openmp unfortunately does not support asynchronous multi-threading as is designed for designed for parallelism, not concurrency. ====Question & Awnser=C++ 11 Threads and OpenMp compatibility===
Can one safely use C++11 multi-threading as well as OpenMP in one and the same program but without
interleaving them (i.e. no OpenMP statement in any code passed to C++11 concurrent features and no
C++11 concurrency in threads spawned by OpenMP)?
On some platforms efficient implementation could only be achieved if the OpenMP run-time is the
and x86 is usually considered an "experimental" platform (other vendors are usually much more conservative).
===Conclusion===