Producer-Consumer Problem in OS: Semaphore Solution With Simple Examples

Operating System Chapter 8 · Bounded Buffer Conveyor Lab
Producer-Consumer Problem in OS: Semaphore Solution With Simple Examples

The Producer-Consumer Problem is a classic process-synchronization problem in which a producer creates items and places them in a shared buffer, while a consumer removes and uses those items. The main challenge is to prevent three errors: the producer must not insert into a full buffer, the consumer must not remove from an empty buffer, and both sides must not modify the shared buffer structure at the same time. A standard semaphore solution uses mutex for mutual exclusion, empty to count free buffer slots, and full to count filled slots. This chapter explains the complete bounded-buffer solution step by step, including initialization, wait/signal order, solved traces, common mistakes and multiple-producer/consumer cases.

Producer Consumer Bounded buffer mutex empty full
Start with a real situation

Imagine a Small Food Counter

Producer-Consumer Problem in OS: Semaphore Solution With Simple Examples

A cook keeps preparing plates. A waiter keeps taking plates for customers. Between them is a rack that can hold only five plates.

Producer

The cook creates new plates and puts them on the rack.

Shared Buffer Only 5 slots are available

Consumer

The waiter removes plates from the rack and delivers them.

Three obvious problems can happen:

Buffer fullThe producer must stop adding new items until space becomes free.
Buffer emptyThe consumer must wait because there is nothing to remove.
Shared buffer updateProducer and consumer must not corrupt buffer pointers or shared data by changing them unsafely at the same time.

This problem is built directly on Semaphores in OS and Critical Section & Mutual Exclusion.

Formal definition

What Is the Producer-Consumer Problem?

The Producer-Consumer Problem is a synchronization problem where one or more producers add items to a shared buffer and one or more consumers remove items from it. The processes must coordinate so the buffer is used safely.

The producer and consumer are concurrent. They may run at different speeds. Sometimes the producer is faster, sometimes the consumer is faster.

Producer adds · Consumer removes · Buffer connects them
Visual buffer

What Does a Bounded Buffer Look Like?

A bounded buffer has a fixed number of slots.

Buffer size = 5
Item A
Item B
Empty
Empty
Empty

In this state:

full = 2 · empty = 3

The total must always remain equal to the buffer capacity:

full + empty = buffer size
The three controls

Why Do We Need Three Semaphores?

mutex Protects the actual critical section where the buffer structure is changed.
empty Counts how many empty buffer slots are available for producers.
full Counts how many filled buffer slots are available for consumers.

For a buffer of size N, the standard initialization is:

mutex = 1 · empty = N · full = 0

Why?

mutex = 1One process at a time may change the shared buffer.
empty = NAt the beginning, every buffer slot is empty.
full = 0At the beginning, no item exists for a consumer.
Producer recipe

Producer Semaphore Solution

The producer should first make an item, then check for an empty slot, then lock the shared buffer, insert the item, unlock the buffer and finally announce that one more full slot exists.

Producer

while (true) {
    produce_item();

    wait(empty);
    wait(mutex);

    add_item_to_buffer();

    signal(mutex);
    signal(full);
}

Meaning

produce item
wait for free slot
lock buffer
insert item
unlock buffer
announce new full slot
Producer: wait(empty) -> wait(mutex) -> insert -> signal(mutex) -> signal(full)
Consumer recipe

Consumer Semaphore Solution

The consumer does the mirror operation. It must wait until an item exists, lock the buffer, remove the item, unlock the buffer, announce one new empty slot, and then consume the removed item.

Consumer

while (true) {
    wait(full);
    wait(mutex);

    remove_item_from_buffer();

    signal(mutex);
    signal(empty);

    consume_item();
}

Meaning

wait for available item
lock buffer
remove item
unlock buffer
announce free slot
use the item
Consumer: wait(full) -> wait(mutex) -> remove -> signal(mutex) -> signal(empty)
Why this order works

Why Does Producer Call wait(empty) Before wait(mutex)?

This order is extremely important.

The producer should first confirm that an empty slot exists and only then lock the buffer. Otherwise it could hold mutex while waiting for a free slot, which can block the consumer from entering the buffer to create that free slot.

Wrong order

Producer takes mutex first, then waits for empty. If the buffer is full, producer may sleep while still holding mutex.

Correct order

Producer waits for empty first. Only after space exists does it take mutex and modify the buffer.

The same logic applies to the consumer: it checks full before locking the buffer.

Solved execution trace

Producer-Consumer Example With Buffer Size 3

Let:

mutex = 1 · empty = 3 · full = 0
Step Action mutex empty full Buffer
1Initial state1300 items
2P1 executes wait(empty)1200 items
3P1 executes wait(mutex)0200 items
4P1 inserts Item A0201 item
5P1 signal(mutex), signal(full)1211 item
6C1 executes wait(full)1201 item
7C1 executes wait(mutex)0201 item
8C1 removes Item A0200 items
9C1 signal(mutex), signal(empty)1300 items

Notice how empty decreases when the producer reserves a slot, while full increases after the item is actually inserted.

Interactive learning

Try the Bounded Buffer Simulator

The demo below uses a buffer capacity of 4. Produce adds one item if space exists. Consume removes one item if the buffer is not empty.

Live bounded buffer

Empty
Empty
Empty
Empty
mutex = 1
empty = 4
full = 0

Execution log

Buffer initialized. mutex = 1, empty = 4, full = 0.
Three different problems

What Exactly Do mutex, empty and full Prevent?

mutex prevents race condition Only one process at a time changes buffer indexes or shared buffer state.
empty prevents overflow Producer waits if no free slot is available.
full prevents underflow Consumer waits if no produced item is available.
mutex = safety · empty = free space · full = available items
Bounded vs unbounded

Bounded Buffer vs Unbounded Buffer

Bounded Buffer

Has a fixed capacity. Producer must wait when the buffer becomes full.

Unbounded Buffer

Conceptually has no practical capacity limit in the model, so the producer does not wait because of a full-buffer condition.

Most operating-system textbook solutions use the bounded-buffer version because it contains all three synchronization problems: full buffer, empty buffer and safe shared access.

Circular buffer connection

How Is a Circular Buffer Used?

Many producer-consumer implementations use a circular array with two indexes:

inPoints to the position where the producer will insert the next item.
outPoints to the position where the consumer will remove the next item.

After reaching the last array position, the index wraps back to the beginning.

in = (in + 1) mod N · out = (out + 1) mod N

Because both indexes are shared buffer information, updating them belongs inside the critical section.

Multiple workers

What If There Are Multiple Producers and Multiple Consumers?

The same three-semaphore idea still works. empty counts free slots, full counts produced items, and mutex protects the shared buffer structure.

For example, P1 and P2 may both produce jobs while C1 and C2 consume them. Several producers can create items outside the critical section at the same time, but only one should change the shared buffer structure at once.

This is one reason the critical section should be kept as small as possible: do not perform slow work inside mutex unless that work really needs exclusive buffer access.

A subtle performance point

Why Is produce_item() Outside the Critical Section?

Producing the item may take time. If the producer locks mutex before creating the item, the consumer cannot access the buffer during that entire production time.

Bad design

Lock buffer -> slowly create item -> insert -> unlock.

Better design

Create item first -> lock only when actually changing the shared buffer.

Keep critical sections short: protect only the truly shared operation.
Why consume_item() is outside too

Why Does the Consumer Release mutex Before consume_item()?

Once the item has been removed safely from the shared buffer, the consumer usually does not need to keep the buffer locked while it processes the item.

Releasing mutex early lets producers and other consumers use the buffer sooner.

Critical section should contain buffer access, not all producer or consumer work.

Wrong orders to understand

Common Wrong Semaphore Orders

Wrong PatternProblemBetter Rule
Producer: wait(mutex) before wait(empty)Can hold mutex while waiting for space and block consumerCheck empty first
Consumer: wait(mutex) before wait(full)Can hold mutex while waiting for an item and block producerCheck full first
Producer forgets signal(full)Consumers may keep waiting even though an item existsSignal full after insertion
Consumer forgets signal(empty)Producers may think no space existsSignal empty after removal
Signal mutex before buffer update endsAnother process can enter too earlyRelease mutex only after shared update is complete
Blocking behavior

What Happens When the Buffer Is Full or Empty?

Buffer Full

empty becomes 0. A producer calling wait(empty) must wait until a consumer removes an item and signals empty.

Buffer Empty

full becomes 0. A consumer calling wait(full) must wait until a producer inserts an item and signals full.

In a blocking semaphore implementation, the waiting process can sleep rather than continuously using CPU time.

Producer-consumer vs simple mutex

Why Is mutex Alone Not Enough?

mutex only prevents simultaneous buffer modification. It does not tell the producer whether space exists or tell the consumer whether an item exists.

mutexAnswers: “Is another process modifying the buffer?”
emptyAnswers: “Is there a free slot for a producer?”
fullAnswers: “Is there an item for a consumer?”
Real systems

Real-Life Uses of the Producer-Consumer Pattern

Print queueApplications produce print jobs; printer service consumes them.
Network packetsNetwork hardware/software produces packets; processing code consumes them.
Keyboard bufferInput device produces characters; application consumes them.
Logging systemApplication threads produce log messages; logging thread writes them.
Job queueClients produce jobs; worker threads consume jobs.
Streaming pipelineOne stage produces data blocks that the next stage consumes.
Common student confusions

Producer-Consumer Mistakes to Avoid

empty and full are not boolean locksThey are counting semaphores representing buffer capacity and item count.
mutex is not the buffer-size countermutex protects shared data access.
full starts at 0No item exists initially.
empty starts at NAll N slots are free initially.
wait(empty) is before wait(mutex)This avoids holding the buffer lock while waiting for space.
wait(full) is before wait(mutex)This avoids holding the buffer lock while waiting for an item.
produce/consume work is usually outside mutexOnly shared-buffer update needs the critical section.
Signal the opposite counter after shared updateProducer signals full; consumer signals empty.
Exam answer blueprint

How to Write Producer-Consumer in an Exam

2 marks

Definition

Define producer, consumer and shared buffer in two or three lines.

5 marks

Semaphore setup

Write mutex = 1, empty = N, full = 0 and explain what each semaphore means.

8-10 marks

Complete solution

Add producer pseudocode, consumer pseudocode, bounded-buffer diagram, solved trace and reason for the wait/signal order.

Fast memory trick

Producer waits for EMPTY · Consumer waits for FULL · Both use MUTEX
Quick self-test

Practice Questions With Answers

1. What is the Producer-Consumer Problem?

It is a synchronization problem where producers add items to a shared buffer and consumers remove items while the operating system prevents overflow, underflow and unsafe concurrent buffer access.

2. What is the initial value of mutex?

mutex is normally initialized to 1 so only one process at a time can modify the shared buffer.

3. What is the initial value of empty for a buffer of size N?

empty is initialized to N because all N buffer slots are free at the beginning.

4. What is the initial value of full?

full is initialized to 0 because no item is present in the buffer initially.

5. Why does the producer execute wait(empty)?

It ensures that at least one free buffer slot exists before the producer inserts an item.

6. Why does the consumer execute wait(full)?

It ensures that at least one produced item exists before the consumer removes an item.

7. Why is mutex required?

mutex protects the critical section where shared buffer data, indexes or pointers are changed.

8. Why is wait(empty) before wait(mutex) important?

It prevents a producer from holding mutex while waiting for free space, which could block the consumer that needs mutex to remove an item.

9. What happens when the buffer is full?

empty becomes 0, so the next producer waits until a consumer removes an item and signals empty.

10. What happens when the buffer is empty?

full becomes 0, so the next consumer waits until a producer inserts an item and signals full.

One-page memory board

Producer-Consumer Revision Board

ProducerCreates and inserts items.
ConsumerRemoves and uses items.
Bounded bufferShared storage with fixed capacity.
mutex = 1Protects shared buffer access.
empty = NCounts free slots.
full = 0Counts available items.
Producer waits on emptyNo overflow.
Consumer waits on fullNo underflow.
Both wait on mutexNo unsafe simultaneous buffer update.
Producer signals fullOne more item became available.
Consumer signals emptyOne more slot became free.
Keep critical section shortBetter concurrency and performance.
Frequently asked questions

Frequently Asked Questions

What is the Producer-Consumer Problem in OS?

The Producer-Consumer Problem is a synchronization problem where producers add items to a shared buffer and consumers remove them while avoiding overflow, underflow and race conditions.

Which semaphores are used in the bounded-buffer solution?

The standard solution uses mutex for mutual exclusion, empty to count free buffer slots and full to count filled buffer slots.

Why is mutex initialized to 1?

mutex is initialized to 1 because only one process at a time should enter the critical section that modifies the shared buffer.

Why is empty initialized to N and full to 0?

At the beginning all N buffer slots are empty and no produced item exists, so empty = N and full = 0.

Why is wait(empty) called before wait(mutex)?

The producer should first make sure space exists before locking the shared buffer. Otherwise it could hold mutex while waiting and block the consumer that needs to free a slot.

Can the Producer-Consumer Problem have multiple producers and consumers?

Yes. The same semaphore idea can coordinate multiple producers and multiple consumers as long as shared buffer access is protected correctly.

Next problem to master

What Should You Learn Next?

Post a Comment

0 Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.
Join Telegram Exam updates and free resources