Fundamentals 9 min read

Master the Producer-Consumer Problem in C with Mutexes, Condition Variables & Semaphores

This article explains the classic producer‑consumer synchronization challenge, describes its importance in concurrent programming, and provides two complete C implementations—one using mutexes and condition variables and another using semaphores—along with detailed code and sample output.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Master the Producer-Consumer Problem in C with Mutexes, Condition Variables & Semaphores

Understanding the Producer‑Consumer Problem

The producer‑consumer problem models a scenario where one or more producer threads generate data items and place them into a fixed‑size shared buffer, while one or more consumer threads remove items for processing. Correct synchronization is required to avoid race conditions, buffer overflows, and deadlocks.

Common Synchronization Primitives

Mutexes protect critical sections by allowing only one thread to access shared data at a time.

Condition variables let a thread wait until a specific condition (e.g., buffer not empty) becomes true.

Semaphores are counting objects that can represent the number of available slots (empty) or items (full) in the buffer.

Bounded Buffer Using Mutexes and Condition Variables

This implementation uses a circular array of size BUFFER_SIZE as the shared buffer. Two condition variables, full and empty, signal when the buffer contains items or has free slots. A single mutex serialises access to the buffer indices.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define BUFFER_SIZE 5
#define MAX_ITEMS 5

int buffer[BUFFER_SIZE];
int in = 0, out = 0;
int produced_count = 0, consumed_count = 0;

pthread_mutex_t mutex;
pthread_cond_t full, empty;

void* producer(void* arg) {
    int item = 1;
    while (produced_count < MAX_ITEMS) {
        pthread_mutex_lock(&mutex);
        while (((in + 1) % BUFFER_SIZE) == out) {
            pthread_cond_wait(&empty, &mutex);
        }
        buffer[in] = item;
        printf("Produced: %d
", item);
        item++;
        in = (in + 1) % BUFFER_SIZE;
        produced_count++;
        pthread_cond_signal(&full);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

void* consumer(void* arg) {
    while (consumed_count < MAX_ITEMS) {
        pthread_mutex_lock(&mutex);
        while (in == out) {
            pthread_cond_wait(&full, &mutex);
        }
        int item = buffer[out];
        printf("Consumed: %d
", item);
        out = (out + 1) % BUFFER_SIZE;
        consumed_count++;
        pthread_cond_signal(&empty);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main() {
    pthread_t prod, cons;
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&full, NULL);
    pthread_cond_init(&empty, NULL);
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&full);
    pthread_cond_destroy(&empty);
    return 0;
}

Typical output (order may vary due to scheduling):

Produced: 1
Produced: 2
Produced: 3
Produced: 4
Consumed: 1
Consumed: 2
Consumed: 3
Consumed: 4
Produced: 5
Consumed: 5

Bounded Buffer Using Semaphores

This variant replaces the mutex/condition‑variable pair with three semaphores: mutex – a binary semaphore providing mutual exclusion. empty – a counting semaphore initialized to BUFFER_SIZE, representing free slots. full – a counting semaphore initialized to 0, representing occupied slots.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#define BUFFER_SIZE 5
#define MAX_ITEMS 20

int buffer[BUFFER_SIZE];
int in = 0, out = 0;
int produced_count = 0, consumed_count = 0;

sem_t mutex, full, empty;

void* producer(void* arg) {
    int item = 1;
    while (produced_count < MAX_ITEMS) {
        sem_wait(&empty);
        sem_wait(&mutex);
        buffer[in] = item;
        printf("Produced: %d
", item);
        item++;
        in = (in + 1) % BUFFER_SIZE;
        produced_count++;
        sem_post(&mutex);
        sem_post(&full);
    }
    return NULL;
}

void* consumer(void* arg) {
    while (consumed_count < MAX_ITEMS) {
        sem_wait(&full);
        sem_wait(&mutex);
        int item = buffer[out];
        printf("Consumed: %d
", item);
        out = (out + 1) % BUFFER_SIZE;
        consumed_count++;
        sem_post(&mutex);
        sem_post(&empty);
    }
    return NULL;
}

int main() {
    pthread_t prod, cons;
    sem_init(&mutex, 0, 1);
    sem_init(&full, 0, 0);
    sem_init(&empty, 0, BUFFER_SIZE);
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
    sem_destroy(&mutex);
    sem_destroy(&full);
    sem_destroy(&empty);
    return 0;
}

The semaphore‑based program produces interleaved Produced and Consumed lines, demonstrating correct synchronization using a different primitive set.

Key Takeaways

Both implementations illustrate how to coordinate producer and consumer threads safely:

Use a mutex (or binary semaphore) to protect shared indices and the buffer array.

Use condition variables or counting semaphores to signal buffer state (empty vs. full).

Terminate the loops after a predefined number of items to avoid infinite execution in examples.

Choosing between mutex/condition‑variable and semaphore approaches depends on the available threading library and personal preference; both achieve the same correctness guarantees for the classic producer‑consumer problem.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

concurrencyCSynchronizationmutexsemaphoreProducer Consumer
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.