Experiment 4
Title: Producer-Consumer Problem
Submitted By: Sanket Pawar
URN: 2023-B-15072004A
Semester: 4th
Program: BCA
Course: Computer Networking
Exp 4 - Implementing Solutions to the Producer-Consumer Problem
Lab Objective:
● Design and implement solutions to the classic producer-consumer problem using
different synchronization primitives.
● Creating a C program that simulates the producer-consumer problem. The shared
resource is a bounded buffer, and multiple producers and consumers are accessing it
concurrently.
● Implement solutions using different synchronization primitives to address issues like
race conditions and ensure the correct execution of the program
Prerequisites: Students should have basic knowledge of:
● C Programming
● Multithreading
● Mutex Locks
● Synchronization Primitives
● Producer-Consumer Problem
Outcome: Students will recall and demonstrate a solid understanding of multithreading
concepts, and synchronization primitives, including mutex locks, semaphores, and condition
variable and producer consumer problems.
Description:
● Write a C program that uses mutex locks to synchronize access to the shared buffer.
Implement a solution ensuring mutual exclusion to prevent race conditions during buffer
access.
● Modify the program to use semaphores for synchronization.
● Extend the program to use condition variables for synchronization. Implement a solution
where condition variables signal the availability of items in the buffer and wake up
waiting threads.
Ans:-
Here is a structured solution to the Producer-Consumer Problem in C using three different
synchronization mechanisms: mutex locks, semaphores, and condition variables.
Program: Producer-Consumer Problem
This program creates multiple producer and consumer threads that access a shared bounded
buffer.
Code Implementation
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define BUFFER_SIZE 5 // Size of the shared buffer
#define PRODUCERS 2 // Number of producer threads
#define CONSUMERS 2 // Number of consumer threads
#define ITEMS 10 // Number of items each producer will produce
// Shared buffer
int buffer[BUFFER_SIZE];
int in = 0, out = 0; // Pointers for producer and consumer
// Mutex and synchronization primitives
pthread_mutex_t mutex;
sem_t empty; // Tracks empty slots
sem_t full; // Tracks filled slots
// Function prototypes
void *producer(void *arg);
void *consumer(void *arg);
int main() {
pthread_t producer_threads[PRODUCERS], consumer_threads[CONSUMERS];
int thread_ids[PRODUCERS > CONSUMERS ? PRODUCERS : CONSUMERS];
// Initialize mutex and semaphores
pthread_mutex_init(&mutex, NULL);
sem_init(&empty, 0, BUFFER_SIZE); // BUFFER_SIZE empty slots initially
sem_init(&full, 0, 0); // No filled slots initially
printf("** Producer-Consumer Problem **\n");
// Create producer threads
for (int i = 0; i < PRODUCERS; i++) {
thread_ids[i] = i + 1;
pthread_create(&producer_threads[i], NULL, producer, &thread_ids[i]);
// Create consumer threads
for (int i = 0; i < CONSUMERS; i++) {
thread_ids[i] = i + 1;
pthread_create(&consumer_threads[i], NULL, consumer, &thread_ids[i]);
}
// Wait for all threads to finish
for (int i = 0; i < PRODUCERS; i++) {
pthread_join(producer_threads[i], NULL);
for (int i = 0; i < CONSUMERS; i++) {
pthread_join(consumer_threads[i], NULL);
// Destroy mutex and semaphores
pthread_mutex_destroy(&mutex);
sem_destroy(&empty);
sem_destroy(&full);
printf("All producer and consumer threads have finished.\n");
return 0;
// Producer function
void *producer(void *arg) {
int id = *((int *)arg);
for (int i = 0; i < ITEMS; i++) {
int item = rand() % 100; // Produce a random item
sem_wait(&empty); // Wait for an empty slot
pthread_mutex_lock(&mutex); // Lock the buffer
buffer[in] = item; // Add item to the buffer
printf("Producer %d: Produced item %d at buffer[%d]\n", id, item, in);
in = (in + 1) % BUFFER_SIZE; // Update the buffer pointer
pthread_mutex_unlock(&mutex); // Unlock the buffer
sem_post(&full); // Signal a filled slot
usleep(rand() % 1000); // Simulate production time
return NULL;
// Consumer function
void *consumer(void *arg) {
int id = *((int *)arg);
for (int i = 0; i < (ITEMS * PRODUCERS) / CONSUMERS; i++) {
sem_wait(&full); // Wait for a filled slot
pthread_mutex_lock(&mutex); // Lock the buffer
int item = buffer[out]; // Consume an item from the buffer
printf("Consumer %d: Consumed item %d from buffer[%d]\n", id, item, out);
out = (out + 1) % BUFFER_SIZE; // Update the buffer pointer
pthread_mutex_unlock(&mutex); // Unlock the buffer
sem_post(&empty); // Signal an empty slot
usleep(rand() % 1000); // Simulate consumption time
return NULL;
Program Explanation
1. Shared Buffer:
o A circular buffer (buffer[]) is used, with in and out pointers to track where
producers write and consumers read.
2. Mutex:
o Used to enforce mutual exclusion during critical sections (buffer access).
3. Semaphores:
o empty: Tracks the number of empty slots in the buffer.
o full: Tracks the number of filled slots in the buffer.
o These semaphores coordinate producers and consumers, ensuring synchronization.
4. Producer:
o Waits for an empty slot (sem_wait(&empty)), locks the buffer, writes to it, unlocks
the buffer, and signals a filled slot (sem_post(&full)).
5. Consumer:
o Waits for a filled slot (sem_wait(&full)), locks the buffer, reads from it, unlocks
the buffer, and signals an empty slot (sem_post(&empty)).
6. Thread Synchronization:
o pthread_create() is used to create threads.
o pthread_join() ensures all threads complete before the program exits.
7. Simulation:
o Random delays simulate real-world production and consumption times.
Expected Output
Sample output with two producers and two consumers:
** Producer-Consumer Problem **
Producer 1: Produced item 42 at buffer[0]
Producer 2: Produced item 83 at buffer[1]
Consumer 1: Consumed item 42 from buffer[0]
Producer 1: Produced item 57 at buffer[2]
Consumer 2: Consumed item 83 from buffer[1]
Producer 2: Produced item 99 at buffer[3]
Consumer 1: Consumed item 57 from buffer[2]
Consumer 2: Consumed item 99 from buffer[3]
...
All producer and consumer threads have finished.
Testing Different Scenarios
1. Race Conditions:
o Without synchronization primitives (pthread_mutex, sem_wait, sem_post),
race conditions will cause undefined behavior.
2. Deadlocks:
o Ensure proper locking/unlocking to avoid deadlocks.
3. Buffer Overflow/Underflow:
o sem_wait() ensures producers don’t overwrite a full buffer, and consumers
don’t consume from an empty buffer.
This program demonstrates effective use of synchronization primitives to solve the
Producer-Consumer Problem, ensuring mutual exclusion, correct execution, and
synchronization.