Skip to content

Semaphores and Producer/Consumer Problem


OSTEP: Chapter 31

The refrigerator example solution is much too complicated. The problem is that the mutual exclusion mechanism was too simple-minded: it used only atomic reads and writes. This is sufficient, but unpleasant. It would be unbearable to extend the fridge mechanism to many processes. Let's look for a more powerful, higher-level mechanism.

Requirements for a mutual exclusion mechanism:

  • Must allow only one process into a critical section at a time.

  • If several requests at once, must allow one process to proceed.

  • Processes must be able to go on vacation outside critical section.

Desirable properties for a mutual exclusion mechanism:

  • Fair: if several processes waiting, let each in eventually.

  • Efficient: do not use up substantial amounts of resources when waiting. E.g. no busy waiting.

  • Simple: should be easy to use (e.g. just bracket the critical sections).

Desirable properties of processes using the mechanism:

  • Always lock before manipulating shared data.

  • Always unlock after manipulating shared data.

  • Do not lock again if already locked.

  • Do not unlock if not locked by you.

  • Do not spend large amounts of time in critical section. E.g. do not go on vacation.


In anything at all, perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away.

Terre des Hommes, 1937

Antoine de Saint-Exupery (1900-1944)

Semaphore: A synchronization variable that takes on positive integer values. Invented by Dijkstra.

  • P(semaphore): an atomic operation that waits for semaphore to become positive, then decrements it by 1. Called sem_wait in pthreads (which we'll use on Linux).

  • V(semaphore): an atomic operation that increments semaphore by 1. Called sem_post in pthreads.

The names come from the Dutch, proberen (test) and verhogen (increment). Semaphores are simple and elegant and allow the solution of many interesting problems. They do a lot more than just mutual exclusion.


Too much milk problem with semaphores:

Processes A & B


  01: P(OKToBuyMilk);
  02: if (NoMilk) BuyMilk();
  03: V(OKToBuyMilk);

Note: OKToBuyMilk must initially be set to 1. What happens if it is not?

Show why there can never be more than one process buying milk at once.

Binary semaphores are those that have only two values, 0 and 1. They are implemented in the same way as regular semaphores except multiple V's will not increase the semaphore to anything greater than one.

Semaphores are not provided by hardware (we will discuss implementation later). But they have several attractive properties:

  • Machine independent.

  • Simple.

  • Powerful. Embody both exclusion and waiting.

  • Correctness is easy to determine.

  • Work with many processes.

  • Can have many different critical sections with different semaphores.

  • Can acquire many resources simultaneously (multiple P's).

  • Can permit multiple processes into the critical section at once, if that is desirable.

Semaphores can be used for things other than simple mutual exclusion. For example, resource allocation: P = allocate resource, V = return resource. More on this later.


Semaphore Example: Producer & Consumer: Consider the operation of an assembly line or pipeline. Processes do not have to operate in perfect lock-step, but a certain order must be maintained. For example, must put wheels on before the hub caps. It is OK for wheel mounter to get ahead, but hub-capper must wait if it gets ahead. Another example: compiler & disk reader.

Producer/Consumer

  • Producer: creates new resources.

  • Consumer: uses up (destroys) copies of a resource.

  • Synchronization: Keeping producer and consumer operating reasonably and safely.

  • Define constraints (definition of what is correct).

  • A Consumer must wait for the buffer to be non-empty, i.e., a Producer must have put something in the buffer. (resource management)

  • A Producer must wait for the buffer to be non-full, i.e., a Consumer must have removed something from the buffer. (resource management)

  • Only one process or thread can fiddle with buffer list at a time. (mutual exclusion)

  • A separate semaphore is used for each constraint.
   const int QSIZE 100;
   Things *buffer[QSIZE];
   semaphore empty(QSIZE),
             full(0),
             mutex(1);
   int first=0, last=0;

   int ModIncr(int v)
   {
     return (v+1)%QSIZE;
   }

  void Enqueue(Thing *item)
  {
     P(empty);
     P(mutex);
     buffer[last] = item;
     last = ModIncr(last);
     V(mutex);
     V(full);
  }

  Thing *Dequeue()
  {
     P(full);
     P(mutex);
     Thing *ret = buff[first];
     first = ModIncr(first);
     V(mutex);
     V(empty);
  }

  • Important questions:

  • Why does Enqueue do an P(empty) but V(full)?

  • Why is order of P's important?

  • Is order of V's important?

  • Do we we need two separate semaphores for waiting when the buffer is empty and full?

Producers and consumers are much like Unix pipes.

THIS IS A VERY IMPORTANT EXAMPLE!


Copyright © 2011, 2018, 2020 Barton P. Miller

Non-University of Wisconsin students and teachers are welcome to print these notes their personal use. Further reproduction requires permission of the author.