Concurrency
Concurrency in C++ is typically achieved using threads. Threads are separate flows of execution that run independently within a process. They share the same memory space, allowing them to communicate and coordinate with each other.
Threads
The <thread>
header provides classes and functions for working with threads in C++.
#include <iostream>
#include <thread>
// Function to be executed by the thread
void printNumbers(int start, int end) {
for (int i = start; i <= end; ++i) {
std::cout << i << " ";
}
std::cout << std::endl;
}
int main() {
// Create two threads and pass the function to be executed by each thread
std::thread t1(printNumbers, 1, 5);
std::thread t2(printNumbers, 6, 10);
// Wait for both threads to finish
t1.join();
t2.join();
return 0;
}
Mutex
A mutex (short for "mutual exclusion") is a synchronization mechanism used in concurrent programming to ensure that only one thread at a time can access a shared resource or a critical section of code.
The purpose of using a mutex is to prevent data corruption and unexpected behavior that can occur when multiple threads attempt to modify shared data simultaneously.
#include <iostream>
#include <thread>
#include <mutex>
// Global variable shared among threads
int sharedData = 0;
// Mutex to protect access to sharedData
std::mutex dataMutex;
// Function to be executed by the thread
void incrementSharedData(int iterations) {
for (int i = 0; i < iterations; ++i) {
// Lock the mutex before accessing sharedData
std::lock_guard<std::mutex> lock(dataMutex);
// Increment sharedData in a thread-safe manner
sharedData++;
}
}
int main() {
// Create two threads and pass the function to be executed by each thread
std::thread t1(incrementSharedData, 1000000);
std::thread t2(incrementSharedData, 1000000);
// Wait for both threads to finish
t1.join();
t2.join();
// Output the final value of sharedData
std::cout << "Final value of sharedData: " << sharedData << std::endl;
return 0;
}
To ensure that the increment operation is thread-safe, a std::lock_guard
is used to lock the dataMutex
before accessing sharedData. This ensures that only one thread can access the shared data at a time, preventing data races.
Condition Variable
A condition variable is a synchronization primitive used for inter-thread communication. It provides a way for threads to block until a certain condition is met, allowing threads to coordinate their activities.
The C++ Standard Library provides the <condition_variable>
header for this purpose.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool dataReady = false;
// Function to be executed by the first thread
void prepareData() {
// Simulate some work
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Lock the mutex and set the condition to true
{
std::lock_guard<std::mutex> lock(mtx);
dataReady = true;
}
// Notify waiting thread that data is ready
cv.notify_one();
}
// Function to be executed by the second thread
void processData() {
// Lock the mutex
std::unique_lock<std::mutex> lock(mtx);
// Wait for the condition to be true
cv.wait(lock, []{ return dataReady; });
// Process the data
std::cout << "Data processing started." << std::endl;
// Unlock the mutex explicitly (unique_lock provides more flexibility)
lock.unlock();
// Continue processing data (out of the critical section)
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "Data processing completed." << std::endl;
}
int main() {
// Create two threads and pass the functions to be executed by each thread
std::thread t1(prepareData);
std::thread t2(processData);
// Wait for both threads to finish
t1.join();
t2.join();
return 0;
}
Code Challenge
Write a C++ program that simulates a simple buffer shared between two threads. One thread (
Producer
) adds items to the buffer, and another thread (Consumer
) removes items from the buffer. Use amutex
to ensure thread safety.