Implementing Producer-Consumer Patterns in C++11

The producer-consumer problem is a classic synchronization challenge in concurrent programimng. This article demonstrates various implementations using C++11 threading and synchronization primitives.

Shared Code

#include <cstdlib>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

constexpr int BUFFER_CAPACITY = 10;
constexpr int TOTAL_ITEMS = 1000;
std::mutex console_mutex;

Single Producer Single Consmuer

struct CircularBuffer {
    int data[BUFFER_CAPACITY];
    size_t read_pos = 0;
    size_t write_pos = 0;
    std::mutex buffer_mutex;
    std::condition_variable not_full;
    std::condition_variable not_empty;
};

CircularBuffer global_buffer;

void produce(CircularBuffer* buf, int value) {
    std::unique_lock<std::mutex> lock(buf->buffer_mutex);
    while (((buf->write_pos + 1) % BUFFER_CAPACITY) == buf->read_pos) {
        {
            std::lock_guard<std::mutex> console_lock(console_mutex);
            std::cout << "Buffer full, waiting..." << std::endl;
        }
        buf->not_full.wait(lock);
    }
    
    buf->data[buf->write_pos] = value;
    buf->write_pos = (buf->write_pos + 1) % BUFFER_CAPACITY;
    buf->not_empty.notify_all();
}

int consume(CircularBuffer* buf) {
    std::unique_lock<std::mutex> lock(buf->buffer_mutex);
    while (buf->write_pos == buf->read_pos) {
        {
            std::lock_guard<std::mutex> console_lock(console_mutex);
            std::cout << "Buffer empty, waiting..." << std::endl;
        }
        buf->not_empty.wait(lock);
    }
    
    int value = buf->data[buf->read_pos];
    buf->read_pos = (buf->read_pos + 1) % BUFFER_CAPACITY;
    buf->not_full.notify_all();
    return value;
}

void producer_thread() {
    for (int i = 1; i <= TOTAL_ITEMS; ++i) {
        produce(&global_buffer, i);
        {
            std::lock_guard<std::mutex> lock(console_mutex);
            std::cout << "Produced item: " << i << std::endl;
        }
    }
}

void consumer_thread() {
    int consumed_count = 0;
    while (consumed_count < TOTAL_ITEMS) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        int item = consume(&global_buffer);
        {
            std::lock_guard<std::mutex> lock(console_mutex);
            std::cout << "Consumed item: " << item << std::endl;
        }
        ++consumed_count;
    }
}

Multiple Consumers Single Producer

struct SharedBuffer {
    int items[BUFFER_CAPACITY];
    size_t read_index = 0;
    size_t write_index = 0;
    size_t produced_count = 0;
    std::mutex buffer_lock;
    std::mutex counter_lock;
    std::condition_variable space_available;
    std::condition_variable data_available;
};

SharedBuffer shared_buffer;

void multi_produce(SharedBuffer* buf, int value) {
    std::unique_lock<std::mutex> lock(buf->buffer_lock);
    while (((buf->write_index + 1) % BUFFER_CAPACITY) == buf->read_index) {
        buf->space_available.wait(lock);
    }
    
    buf->items[buf->write_index] = value;
    buf->write_index = (buf->write_index + 1) % BUFFER_CAPACITY;
    buf->data_available.notify_all();
}

int multi_consume(SharedBuffer* buf) {
    std::unique_lock<std::mutex> lock(buf->buffer_lock);
    while (buf->write_index == buf->read_index) {
        buf->data_available.wait(lock);
    }
    
    int value = buf->items[buf->read_index];
    buf->read_index = (buf->read_index + 1) % BUFFER_CAPACITY;
    buf->space_available.notify_all();
    return value;
}

void producer_worker() {
    for (int i = 1; i <= TOTAL_ITEMS; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        multi_produce(&shared_buffer, i);
        {
            std::lock_guard<std::mutex> lock(console_mutex);
            std::cout << "Producer " << std::this_thread::get_id() 
                     << " produced: " << i << std::endl;
        }
    }
}

void consumer_worker() {
    while (true) {
        std::unique_lock<std::mutex> lock(shared_buffer.counter_lock);
        if (shared_buffer.produced_count >= TOTAL_ITEMS) break;
        
        int item = multi_consume(&shared_buffer);
        ++shared_buffer.produced_count;
        lock.unlock();
        
        {
            std::lock_guard<std::mutex> console_lock(console_mutex);
            std::cout << "Consumer " << std::this_thread::get_id() 
                     << " consumed: " << item << std::endl;
        }
    }
}

Implemantation Notes

  • Buffer capacity of N can actually hold N-1 items due to circular buffer implementation constraints
  • Console output requires synchronization across threads
  • Condition variables prevent busy waiting and enable efficient thread coordination
  • Proper lock management ensures thread safety and prevents deadlocks

Tags: C++11 multithreading Synchronization condition_variable mutex

Posted on Wed, 14 Oct 2026 16:28:04 +0000 by andrewtayloruk