CS 537 Notes, Section #6: 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:

Desirable properties for a mutual exclusion mechanism:

Desirable properties of processes using the mechanism:


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.


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:

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
   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);
  }

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.