C++ Producer-Consumer Pattern: Real-World Engineering Guide

Problem Solved

Decouple work generation from work processing. Allows producers to generate work at their own pace while consumers process it independently.

How It Works

  • Producers push tasks into a thread-safe queue
  • Consumers take tasks from the queue and process them
  • Uses blocking queues and condition variables for synchronization

STL Usage

#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
using namespace std;

template<typename T>
class ProducerConsumerQueue {
private:
    queue<T> queue_;
    mutex mtx_;
    condition_variable not_empty_;
    condition_variable not_full_;
    size_t max_size_;

public:
    explicit ProducerConsumerQueue(size_t max_size = 1000) 
        : max_size_(max_size) {}

    void push(const T& item) {
        unique_lock<mutex> lock(mtx_);
        not_full_.wait(lock, [this]() { return queue_.size() < max_size_; });
        queue_.push(item);
        not_empty_.notify_one();
    }

    T pop() {
        unique_lock<mutex> lock(mtx_);
        not_empty_.wait(lock, [this]() { return !queue_.empty(); });
        T item = queue_.front();
        queue_.pop();
        not_full_.notify_one();
        return item;
    }
};

Example

#include <atomic>
#include <iostream>
using namespace std;

void producerConsumerExample() {
    ProducerConsumerQueue<int> queue(100);
    atomic<bool> done{false};

    // Producer
    thread producer([&]() {
        for (int i = 0; i < 1000; ++i) {
            queue.push(i);
        }
        done = true;
    });

    // Consumer
    thread consumer([&]() {
        while (!done || !queue.empty()) {
            int item = queue.pop();
            processItem(item);
        }
    });

    producer.join();
    consumer.join();
}

Use Cases

  • Logging systems: Log producers write to queue, logger thread processes
  • Task queues: Task generators enqueue, worker threads dequeue
  • Pipeline stages: Each stage is producer for next, consumer of previous
  • Event processing: Event generators → event processors

Key Takeaways

  • Decouples production and consumption rates
  • Handles load spikes by buffering
  • Supports multiple producers/consumers
  • Essential for async processing

Things to Be Careful About

  • Queue size: Unbounded queues can cause memory issues
  • Shutdown: Ensure all items processed before shutdown
  • Deadlock: Multiple queues can cause circular waits
  • Lost items: Handle exceptions in consumers carefully

Summary

Producer-Consumer is the foundation of many concurrent systems, providing decoupling and buffering between work generation and processing.