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: !

🧠 Test Your Knowledge

What does std::async return?