We can model the state of the buffer with semaphores as follows:
const int BUFFER_CAPACITY = 100; // Say, for example.Essentially the first semaphore counts the number of slots in the buffer which are empty. This will allow the producer to WAIT for an empty slot, and allow the consumer to SIGNAL the producer to produce more items. Initially the buffer is empty (we assume), so the emptySlots semaphore has its counter initially set to BUFFER_CAPACITY.
Semaphore emptySlots = new Semaphore(BUFFER_CAPACITY);
Semaphore fullSlots = new Semaphore(0);
Semaphore mutualExclusion = new Semaphore(1);
The second semaphore counts the number of slots in the buffer which contain an item. The producer can use it to SIGNAL the consumer, in case the consumer is WAITing on an item.
The third semaphore is simply to ensure that both processes do not attempt to update the buffer simultaneously. It is ensuring mutual exclusion while in the critical section of updating the shared buffer. As before.
the code for the producer looks like this
void producer(){and for the consumer:
for(/*ever*/;;){
Object item = produceItem();
wait(emptySlots); // Wait for buffer space.
wait(mutualExclusion); // Enter critical section.
buffer.insertItem(item);
signal(mutualExclusion); // Leave critical section.
signal(fullSlots); // Signal the consumer.
}
}
void consumer(){Note: order of 'wait's is important, otherwise we're asking for deadlock.
for(/*ever*/;;){
wait(fullSlots); // Wait for item in buffer.
wait(mutualExclusion); // Enter critical section.
Object item = buffer.removeItem();
signal(mutualExclusion); // Leave critical section.
signal(emptySlots); // Signal the producer.
consumeItem(item);
}
}
Conversely, is the producer is too slow, and the consumer tries to consume too many items, eventually the 'fullSlots' semaphore goes to 0 and the consumer is forced to wait til the producer sticks something in the buffer and does a SIGNAL on the 'fullSlots' semaphore.
If the buffer is neither full nor empty, the producer should be allowed to enter items in the buffer, and the consumer should be allowed to remove them. Indeed this is the case, since both semaphores would be non-zero, the producer is able to WAIT on the 'emptySlots' semaphore without blocking, and the consumer is allowed to WAIT on the 'fullSlots' semaphore without blocking.
What about the other semaphore, 'mutualExclusion'? That's just there to enable both processes access to the shared data structure which is the buffer. (Otherwise, one process might be attempting to, for example, increment some 'numberOfItemsInBuffer' variable, while the other is attempting to decrement it, leading to race condition hell.)
So semaphores have two roles in this example: