Implement a producer-consumer problem using semaphores
This is one of the most common RTOS and Embedded C interview questions.
Problem Statement
There are two tasks:
- Producer Task → Produces data
- Consumer Task → Consumes data
They communicate through a shared buffer.
Requirements:
- Producer should not write when the buffer is full.
- Consumer should not read when the buffer is empty.
- Both should not access the buffer simultaneously.
To solve this, we use three semaphores:
- Mutex → Protects the shared buffer.
- Empty → Counts available empty slots.
- Full → Counts filled slots.
Example
Buffer size = 5
Initially
Buffer = [ _ _ _ _ _ ] Empty = 5 Full = 0 Mutex = 1
Synchronization
Producer
Wait(Empty) Wait(Mutex) Add item Signal(Mutex) Signal(Full)
Consumer
Wait(Full) Wait(Mutex) Remove item Signal(Mutex) Signal(Empty)
C Implementation (Generic RTOS)
#include <stdio.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 5 int buffer[BUFFER_SIZE]; int in = 0; int out = 0; sem_t empty; sem_t full; sem_t mutex; void* producer(void* arg) { int item = 1; while (1) { sem_wait(&empty); sem_wait(&mutex); buffer[in] = item; printf("Produced %d at %d\n", item, in); in = (in + 1) % BUFFER_SIZE; item++; sem_post(&mutex); sem_post(&full); sleep(1); } } void* consumer(void* arg) { while (1) { sem_wait(&full); sem_wait(&mutex); int item = buffer[out]; printf("Consumed %d from %d\n", item, out); out = (out + 1) % BUFFER_SIZE; sem_post(&mutex); sem_post(&empty); sleep(2); } } int main() { pthread_t p, c; sem_init(&empty, 0, BUFFER_SIZE); sem_init(&full, 0, 0); sem_init(&mutex, 0, 1); pthread_create(&p, NULL, producer, NULL); pthread_create(&c, NULL, consumer, NULL); pthread_join(p, NULL); pthread_join(c, NULL); return 0; }
Execution Example
Produced 1 Produced 2 Produced 3 Consumed 1 Produced 4 Produced 5 Consumed 2 Consumed 3 Produced 6
Semaphore Values During Execution
Initially
| Semaphore | Value |
|---|---|
| Empty | 5 |
| Full | 0 |
| Mutex | 1 |
After Producer inserts one item
| Semaphore | Value |
|---|---|
| Empty | 4 |
| Full | 1 |
| Mutex | 1 |
After Consumer removes one item
| Semaphore | Value |
|---|---|
| Empty | 5 |
| Full | 0 |
| Mutex | 1 |
Why Three Semaphores?
1. Empty Semaphore
Keeps track of available buffer slots.
Producer waits if Empty == 0
Buffer Full
XXXXX
Producer blocks.
2. Full Semaphore
Keeps track of produced items.
Consumer waits if Full == 0
Buffer Empty
_____
Consumer blocks.
3. Mutex
Protects the critical section.
Without mutex
Producer | Buffer | Consumer
Both may modify buffer indices simultaneously, leading to race conditions.
RTOS Version (FreeRTOS)
SemaphoreHandle_t xMutex; SemaphoreHandle_t xEmpty; SemaphoreHandle_t xFull;
Producer
xSemaphoreTake(xEmpty, portMAX_DELAY); xSemaphoreTake(xMutex, portMAX_DELAY); /* Add item to buffer */ xSemaphoreGive(xMutex); xSemaphoreGive(xFull);
Consumer
xSemaphoreTake(xFull, portMAX_DELAY); xSemaphoreTake(xMutex, portMAX_DELAY); /* Remove item */ xSemaphoreGive(xMutex); xSemaphoreGive(xEmpty);
AUTOSAR/OSEK Perspective
OSEK/AUTOSAR OS does not provide POSIX-style semaphores. Instead, similar synchronization is achieved using:
-
Resources (
GetResource()/ReleaseResource()) for mutual exclusion. -
Events (
WaitEvent()/SetEvent()) for task synchronization. - Counters and Alarms for timing-related activation.
The producer-consumer concept remains the same, but the implementation uses AUTOSAR OS primitives rather than semaphores.
Time Complexity
- Produce: O(1)
- Consume: O(1)
- Semaphore operations: O(1)
Interview Answer (2 Minutes)
"The producer-consumer problem demonstrates synchronization between two concurrent tasks sharing a bounded buffer. The producer adds items, while the consumer removes them. To avoid race conditions and buffer overflow/underflow, we use three synchronization primitives: a mutex to ensure exclusive access to the shared buffer, an empty semaphore to count available buffer slots, and a full semaphore to count available items. The producer waits on the empty semaphore before producing and signals the full semaphore after adding an item. The consumer waits on the full semaphore before consuming and signals the empty semaphore after removing an item. This guarantees thread-safe access, prevents overflow and underflow, and is a classic synchronization pattern used in RTOS-based systems."
Post a Comment