Introduction
The concept of "concurrency" refers to a program's ability to execute multiple tasks simultaneously. In C++, concurrency is facilitated through the thread class within the standard library. Threads, which are the fundamental units of execution in a program, can run concurrently with one another. This article will delve extensively into the topic of concurrency in C++.
Creating Threads
To initiate a thread in C++, the initial step involves specifying the function intended for thread execution, followed by the creation of the thread object. The function designated for execution must have a return type of void. Below is a demonstration showcasing the process of creating a thread that executes the function foo:
Code:
#include <iostream>
#include <thread>
void foo() {
std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t(foo);
std::cout << "Hello from main thread " << std::this_thread::get_id() << std::endl;
t.join();
return 0;
}
In this example, a distinct thread is generated using the std::thread constructor Object { [native code] } to execute the function foo. As the auxiliary thread runs foo, the primary thread continues its execution. To ensure that the program concludes only after the thread has completed its task, utilize the join method.
Synchronization
To avoid simultaneous access to shared resources, concurrent applications often require coordinating the running of threads. C++ offers synchronization tools such as mutexes, condition variables, and atomic operations to facilitate this coordination.
Mutexes
A mutex, short for "mutual exclusion," serves as a fundamental synchronization tool designed to avoid simultaneous access to shared resources by multiple threads. It provides two main operations: Lock and Unlock, which enable a thread to gain exclusive access to the protected resource by acquiring the mutex and releasing it once the task is completed.
Here is a visual representation demonstrating the utilization of a mutex to restrict simultaneous access by multiple threads to a shared variable:
Code:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int shared = 0;
void increment() {
mtx.lock();
shared++;
mtx.unlock();
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Shared value is " << shared << std::endl;
return 0;
}
In this scenario, a mutex is employed by a pair of threads to increment a shared variable ensuring exclusive access for one thread at a time. As a result, the shared variable is incremented twice, culminating in a final value of 2.
Condition Variables
Another tool for coordinating thread execution is the condition variable. It offers two functions: wait and notify one. When a thread calls the wait function on a condition variable, it will be blocked until another thread invokes the notify one function on the same condition variable.
Here is a depiction of how to establish a fundamental producer-consumer queue employing condition variables:
Code:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
void producer() {
for (int i = 0; i < 10; i++) {
std::unique_lock<std::mutex> lock(mtx);
q.push(i);
cv.notify_one();
}
}
void consumer() {
for (int i = 0; i < 10; i++) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !
In this visual representation, the consumer thread pauses until data is ready for consumption, following the producer thread's action of adding elements to a queue. Once the data is prepared for processing, the consumer thread receives a notification through the condition variable cv.
Atomic Operations
An alternative technique for coordinating access to shared resources is by utilizing atomic operations. An atomic operation is a singular, unified process that occurs independently without interference from other threads. In C++, integers, booleans, and pointers are among the basic data types that can undergo atomic operations.
Here is a demonstration of incrementing a shared variable using an atomic integer:
Code:
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> shared(0);
void increment() {
shared++;
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Shared value is " << shared << std::endl;
return 0;
}
The shared variable in this scenario is an atomic integer, enabling multiple threads to safely increment it without requiring a mutex.
Conclusion
In summary, the concurrency functionality in C++ is robust, enabling the execution of multiple tasks simultaneously. C++ provides various techniques for managing concurrency such as threads, mutexes, conditional variables, and atomic operations. Understanding these concepts is essential for developing efficient and responsive concurrent programs in C++.