Concurrency
Advanced thread synchronization and communication
🔄 What is Concurrency?
Concurrency provides tools for thread synchronization, communication, and coordination using futures, async operations, mutexes, and condition variables for safe parallel programming.
#include <future>
#include <iostream>
auto future = std::async(std::launch::async, []() {
return 42;
});
std::cout << future.get() << std::endl; // Output: 42
Output:
42
Concurrency Tools
std::future
Get results from async operations
auto result = future.get();
std::async
Run functions asynchronously
std::async(std::launch::async, func);
std::mutex
Protect shared resources
std::lock_guard<std::mutex> lock(mtx);
condition_variable
Thread communication and waiting
cv.wait(lock, condition);
🔹 std::async and std::future Example
std::async launches asynchronous tasks, returning a std::future for result retrieval. This model simplifies parallel execution by abstracting thread management. The future object can block until the task completes, allowing seamless integration of concurrent computations into sequential code, ideal for task‑based parallelism.
#include <iostream>
#include <future>
#include <chrono>
int expensiveCalculation(int n) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // Simulate work
return n * n * n;
}
int main() {
std::cout << "Starting async calculations..." << std::endl;
// Launch async tasks
auto future1 = std::async(std::launch::async, expensiveCalculation, 5);
auto future2 = std::async(std::launch::async, expensiveCalculation, 10);
auto future3 = std::async(std::launch::async, expensiveCalculation, 15);
std::cout << "Tasks launched, doing other work..." << std::endl;
// Do other work while calculations run
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "Other work completed" << std::endl;
// Get results (will wait if not ready)
std::cout << "5^3 = " << future1.get() << std::endl;
std::cout << "10^3 = " << future2.get() << std::endl;
std::cout << "15^3 = " << future3.get() << std::endl;
return 0;
}
Output:
Starting async calculations...
Tasks launched, doing other work...
Other work completed
5^3 = 125
10^3 = 1000
15^3 = 3375
🔹 Mutex for Thread Safety
Mutexes (std::mutex) protect shared data from race conditions by enforcing exclusive access. Threads must lock the mutex before modifying shared resources, preventing simultaneous writes. This synchronization primitive is fundamental for correct concurrent programs, ensuring data consistency in multi‑threaded environments.
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
class SafeCounter {
private:
int count = 0;
std::mutex mtx;
public:
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++count;
std::cout << "Count: " << count << " (Thread: "
<< std::this_thread::get_id() << ")" << std::endl;
}
int getValue() {
std::lock_guard<std::mutex> lock(mtx);
return count;
}
};
int main() {
SafeCounter counter;
std::vector<std::thread> threads;
// Create multiple threads that increment the counter
for (int i = 0; i < 5; ++i) {
threads.emplace_back([&counter]() {
for (int j = 0; j < 2; ++j) {
counter.increment();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
}
// Wait for all threads
for (auto& t : threads) {
t.join();
}
std::cout << "Final count: " << counter.getValue() << std::endl;
return 0;
}
Output:
Count: 1 (Thread: 140234567890432)
Count: 2 (Thread: 140234567890432)
Count: 3 (Thread: 140234559497728)
...
Final count: 10
🔹 Condition Variable Example
Condition variables (std::condition_variable) enable threads to wait for notifications. They are used with mutexes to implement producer‑consumer patterns, where one thread signals another about data availability. This avoids busy‑waiting and efficiently coordinates thread execution based on state changes.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
class MessageQueue {
private:
std::queue<std::string> queue;
std::mutex mtx;
std::condition_variable cv;
public:
void produce(const std::string& message) {
std::lock_guard<std::mutex> lock(mtx);
queue.push(message);
std::cout << "Produced: " << message << std::endl;
cv.notify_one(); // Wake up waiting consumer
}
std::string consume() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this] { return !queue.empty(); }); // Wait for message
std::string message = queue.front();
queue.pop();
std::cout << "Consumed: " << message << std::endl;
return message;
}
};
int main() {
MessageQueue mq;
// Consumer thread
std::thread consumer([&mq]() {
for (int i = 0; i < 3; ++i) {
mq.consume();
}
});
// Producer thread
std::thread producer([&mq]() {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
mq.produce("Hello");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
mq.produce("World");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
mq.produce("!");
});
consumer.join();
producer.join();
return 0;
}
Output:
Produced: Hello
Consumed: Hello
Produced: World
Consumed: World
Produced: !
Consumed: !