In this post, we implement a simulation of the Producer-Consumer Problem in C++ using a circular bounded buffer. This is one of the most classic synchronization problems in operating systems, modeling the coordination between a process that generates data (producer) and a process that consumes data (consumer) through a shared buffer of fixed capacity.
What is the Producer-Consumer Problem?
The Producer-Consumer problem (also called the Bounded Buffer problem) defines these rules:
- The producer generates items and places them into the buffer β but only if space is available. If the buffer is full, the producer must wait (sleep).
- The consumer retrieves and processes items from the buffer β but only if items exist. If the buffer is empty, the consumer must wait (sleep).
- Only one entity should access the buffer at a time (mutual exclusion).
This simulation runs 20 steps. At each step, a random number determines whether the producer or consumer acts. A circular buffer is used so the array can be reused efficiently without shifting elements.
C++ Code Implementation
// ============================================================
// Producer-Consumer Problem in C++ (Single-threaded Simulation)
// Uses a circular bounded buffer; random decisions simulate
// concurrent producer/consumer behavior without real threads
// ============================================================
#include <iostream>
#include <cstdlib> // for rand()
using namespace std;
int main() {
int buffer[50]; // The shared circular buffer
int bufferSize; // Maximum capacity of the buffer
cout << "\n Enter Buffer Size: ";
cin >> bufferSize;
int itemCount = 0; // Current number of items in the buffer
int producePtr = 0; // Points to the next write position (producer's index)
int consumePtr = 0; // Points to the next read position (consumer's index)
int stepCounter = 1; // Tracks simulation step number (items are numbered by step)
// -------------------------------------------------------
// Simulation: run for 20 steps
// -------------------------------------------------------
while (stepCounter <= 20) {
// --- Display current buffer contents ---
// Walk the circular buffer from consumePtr to producePtr
cout << "\n\n Buffer: ";
int displayIdx = consumePtr % bufferSize;
while (displayIdx != producePtr % bufferSize) {
cout << buffer[displayIdx] << "\t";
displayIdx++;
if (displayIdx == bufferSize) displayIdx = 0; // Wrap around
}
// --- Random decision: even = producer acts, odd = consumer acts ---
int randomDecision = rand() % 100;
stepCounter++;
if (randomDecision % 2 == 0) {
// ---- Producer's turn ----
if (itemCount != bufferSize) {
// Buffer not full: produce a new item
buffer[producePtr % bufferSize] = stepCounter; // Place item at produce pointer
cout << "\n Producer Produced: " << stepCounter;
producePtr++; // Advance the produce pointer
itemCount++; // One more item in the buffer
} else {
// Buffer full: producer must wait
cout << "\n Producer is Sleeping (Buffer Full)";
}
} else {
// ---- Consumer's turn ----
if (itemCount != 0) {
// Buffer not empty: consume the oldest item
int consumedItem = buffer[consumePtr % bufferSize]; // Read from consume pointer
cout << "\n Consumer Consumed: " << consumedItem;
consumePtr++; // Advance the consume pointer
itemCount--; // One fewer item in the buffer
} else {
// Buffer empty: consumer must wait
cout << "\n Consumer is Sleeping (Buffer Empty)";
}
}
}
cout << endl;
return 0;
}
Explanation of the Code
- Circular Buffer β The buffer is accessed using
producePtr % bufferSizeandconsumePtr % bufferSize. As the pointers grow, the modulo operation wraps them back to the start of the array, allowing the fixed-size buffer to be reused indefinitely without shifting data. itemCountβ Tracks how many items are currently in the buffer.itemCount == bufferSizemeans full (producer sleeps).itemCount == 0means empty (consumer sleeps). This is the semaphore analog in a real synchronization solution.- Random decision β
rand() % 100produces a value 0β99. Even numbers trigger the producer; odd numbers trigger the consumer. This simulates unpredictable concurrent scheduling without actual threads. - Buffer display β Walks from
consumePtr % bufferSizetoproducePtr % bufferSize, printing all items currently in the buffer (oldest to newest). Theif (displayIdx == bufferSize) displayIdx = 0handles wrap-around during display. - Variable names β
producePtr/consumePtrclearly indicate their role.itemCountis self-explanatory.randomDecisionmakes the intent of the random number obvious.
Sample Output
Enter Buffer Size: 6
Buffer:
Consumer is Sleeping (Buffer Empty)
Buffer:
Consumer is Sleeping (Buffer Empty)
Buffer:
Producer Produced: 4
Buffer: 4
Producer Produced: 5
Buffer: 4 5
Consumer Consumed: 4
Buffer: 5
Producer Produced: 7
Buffer: 5 7
Producer Produced: 8
Buffer: 5 7 8
Producer Produced: 9
Buffer: 5 7 8 9
Producer Produced: 10
Buffer: 5 7 8 9 10
Producer Produced: 11
Buffer: 5 7 8 9 10 11
Consumer Consumed: 5
Buffer: 7 8 9 10 11
Consumer Consumed: 7
Buffer: 8 9 10 11
Consumer Consumed: 8
Buffer: 9 10 11
Consumer Consumed: 9
Buffer: 10 11
Consumer Consumed: 10
Buffer: 11
Consumer Consumed: 11
Buffer:
Consumer is Sleeping (Buffer Empty)
Buffer:
Producer Produced: 19
Buffer: 19
Consumer Consumed: 19
Step-by-Step Explanation of Input/Output
- Steps 1β2: Random selects consumer both times. Buffer is empty β Consumer sleeps both times.
- Steps 3β4: Producer gets selected. Items 4 and 5 are produced and placed in the buffer.
- Step 5: Consumer consumes item 4 (oldest = front of circular buffer).
- Steps 6β11: Producer keeps getting selected. Items 7β11 fill the buffer to capacity (6 items).
- Steps 12β17: Consumer repeatedly selected. Items 5, 7, 8, 9, 10, 11 consumed one by one until buffer empties.
- Step 18: Consumer selected but buffer empty β sleeps.
- Steps 19β20: Producer produces item 19; consumer immediately consumes it.
Note: The output will differ on each run because rand() produces a different sequence unless explicitly seeded with srand().
See Also
Bottom line isβ¦
This simulation captures the core logic of the bounded buffer problem in a single-threaded program. In a real concurrent system, the itemCount variable would be replaced with semaphores (sem_wait / sem_post in POSIX or BlockingQueue in Java) to ensure thread-safe access. The producer-consumer pattern appears everywhere in software: message queues, log pipelines, I/O buffers, and event-driven architectures all rely on the same principle of decoupling producers from consumers through a bounded buffer.
To know the process of the implementation if your device of the Apple is not working well then you should know that from the help of the google drive tech help to gather the knowledge from there.