Why do we need 2 variables for Semaphores in the Producer Consumer problem?

Viewed 684

The standard way Producer Consumer is implemented is like:

  • useQueue mutex
  • emptyCount semaphore of size N
  • fullCount semaphore of size N

produce:

down(emptyCount)
down(useQueue)
putItemIntoQueue(item)
up(useQueue)
up(fullCount)

consume:

down(fullCount)
down(useQueue)
item ← getItemFromQueue()
up(useQueue)
up(emptyCount)

Where if down has a non-positive value, the thread waits. up pushes the count up

Taken from this Wikipedia article

Why can't we have something like:

class NewSemaphore {
    int capacity, permits;

    /**
     * Initialize the semaphore with a max capacity
     * @param n the max capacity
     */
    NewSemaphore(int n) {
        capacity = n;
        permits = 0;
    }

    /**
     * We usually never check this. Check if it's within limits.
     * If not, wait
     */
    synchronized void up() {
        if (permits >= capacity) {
            wait();
        } else {
            permits++;
            notify();
        }
    }

    /**
     * Standard down/acquire function
     */
    synchronized void down() {
        if (permits <= 0) {
            wait();
        } else {
            permits--;
            notify();
        }
    }
}

This will be called like:

produce:

up(mySemaphore)
down(useQueue)
putItemIntoQueue(item)
up(useQueue)

consume:

down(mySemaphore)
down(useQueue)
item ← getItemFromQueue()
up(useQueue)

Why do we need 2 different variables emptyCount and fullCount?

2 Answers

There are two semaphores because there are two things we are keeping in check. First is that consumers wait if there is nothing to consume, and second that producers wait if the queue is full.

Your idea would let producers continue producing until they ran out of memory or some other resource.

Related