Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Concurrency

Condition Variables and Futures

Advanced Thread Synchronization

Beyond mutexes and atomics, C++ provides higher-level synchronization primitives: condition variables for signaling between threads, and futures/promises for asynchronous result retrieval.

Condition Variables

Condition variables enable threads to wait for specific conditions without busy-waiting:

#include <condition_variable>
#include <mutex>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> queue;

void producer() {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            queue.push(i);
        }
        cv.notify_one();  // Wake one waiting thread
    }
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !queue.empty(); });  // Wait until queue not empty
        
        int value = queue.front();
        queue.pop();
        lock.unlock();
        
        std::cout << "Consumed: " << value << '\\n';
    }
}

Wait Mechanics

std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock);  // Atomically unlocks mtx and sleeps
                // When woken, reacquires mtx

// Equivalent to:
while (!predicate()) {
    cv.wait(lock);
}

Spurious Wakeups

Condition variables can wake spuriously (without notify). Always use predicate form:

// Bad: No protection against spurious wakeups
cv.wait(lock);
if (!queue.empty()) { /* ... */ }

// Good: Handles spurious wakeups
cv.wait(lock, []{ return !queue.empty(); });

notify_one vs notify_all

cv.notify_one();   // Wakes one waiting thread
cv.notify_all();   // Wakes all waiting threads

Use notify_all when multiple threads might need to act on the condition.

Timed Waits

using namespace std::chrono_literals;

std::unique_lock<std::mutex> lock(mtx);

if (cv.wait_for(lock, 1s, []{ return ready; })) {
    // Condition met within 1 second
} else {
    // Timeout
}

auto deadline = std::chrono::system_clock::now() + 5s;
if (cv.wait_until(lock, deadline, []{ return ready; })) {
    // Condition met before deadline
}

std::future and std::promise

Futures provide a mechanism to retrieve results from asynchronous operations:

promise-future Pair

#include <future>

void compute(std::promise<int> prom) {
    // Do expensive computation
    int result = 42;
    prom.set_value(result);  // Fulfill promise
}

std::promise<int> prom;
std::future<int> fut = prom.get_future();

std::thread t(compute, std::move(prom));

int result = fut.get();  // Blocks until promise fulfilled
std::cout << "Result: " << result << '\\n';

t.join();

Exception Propagation

void task(std::promise<int> prom) {
    try {
        throw std::runtime_error("Error!");
    } catch (...) {
        prom.set_exception(std::current_exception());
    }
}

std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(task, std::move(prom));

try {
    int result = fut.get();  // Rethrows exception
} catch (const std::exception& e) {
    std::cout << "Caught: " << e.what() << '\\n';
}

t.join();

std::async

Higher-level alternative to manual thread + promise:

std::future<int> fut = std::async(std::launch::async, []{
    // Runs in separate thread
    return 42;
});

int result = fut.get();  // Retrieve result

Launch Policies

// Force async execution (new thread)
auto f1 = std::async(std::launch::async, task);

// Defer execution until get() called (lazy)
auto f2 = std::async(std::launch::deferred, task);

// Implementation chooses (default)
auto f3 = std::async(task);

shared_future

Multiple threads can wait on the same result:

std::shared_future<int> sf = std::async(task).share();

auto lambda = [sf]{ return sf.get(); };

std::thread t1(lambda);
std::thread t2(lambda);
// Both can call get()

t1.join();
t2.join();

packaged_task

Wraps callable for future retrieval:

std::packaged_task<int(int, int)> task([](int a, int b) {
    return a + b;
});

std::future<int> result = task.get_future();

std::thread t(std::move(task), 3, 4);

std::cout << result.get();  // 7

t.join();

Producer-Consumer with Condition Variables

class ThreadSafeQueue {
    std::queue<int> queue_;
    mutable std::mutex mtx_;
    std::condition_variable cv_;
    
public:
    void push(int value) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            queue_.push(value);
        }
        cv_.notify_one();
    }
    
    int pop() {
        std::unique_lock<std::mutex> lock(mtx_);
        cv_.wait(lock, [this]{ return !queue_.empty(); });
        int value = queue_.front();
        queue_.pop();
        return value;
    }
    
    bool empty() const {
        std::lock_guard<std::mutex> lock(mtx_);
        return queue_.empty();
    }
};
Conceptual Check

Why must condition_variable::wait take a unique_lock instead of lock_guard?

Runtime Environment

Interactive Lab

1#include <iostream>
2#include <future>
3#include <thread>
4#include <chrono>
5 
6int compute(int x) {
7 std::this_thread::sleep_for(std::chrono::milliseconds(100));
8 return x * x;
9}
10 
11int main() {
12 // Async execution
13 auto fut1 = std::async(std::launch::async, compute, 5);
14 auto fut2 = std::async(std::launch::async, compute, 10);
15
16 std::cout << "Computing...\n";
17
18 std::cout << "Result 1: " << fut1.get() << '\n';
19 std::cout << "Result 2: " << fut2.get() << '\n';
20
21 return 0;
22}
System Console

Waiting for signal...