html>

Synchronization with Semaphores

The too-much-milk 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 that mechanism to many processes. Let's look at more powerful, higher-level mechanisms.

Requirements for a mutual exclusion mechanism:

Desirable properties for a mutual exclusion mechanism:

Desirable properties of processes using the mechanism:

The Binary Semaphore

A synchronization variable that takes only the values 0 and 1. Invented by Edsger Dijkstra in the mid 60's. It has two atomic operations:

P(s): If s==1 then set s=0 and proceed; else wait.

V(s): If there is a thread waiting on s, then wake one such thread; else set s = 1.

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
1       P(mutex);
2	     if (NoMilk) {
3		  BuyMilk;
4	}
5       V(mutex);

Note: mutex must initially be set to 1. What happens if it isn't?

Semaphores aren't provided by hardware. But they have several attractive properties:

Semaphores are used in two different ways:

The Counting Semaphore

A synchronization variable that takes on nonnegative integer values. Again, there are only two atomic operations allowed on the variable.

P(s): If s>0 then set s = s-1 and proceed; else wait.

V(s): If there is a thread waiting on s, then wake one such thread; else set s = s+1.


Counting Semaphore Example: Producer & Consumer.

Suppose one thread is creating information that is going to be used by another thread, e.g. suppose one thread is reading information from the disk, and another thread will compile that information from soure code to binary. The treads shouldn't have to operate in perfect lock-step: The producer should be able to get ahead of the consumer.

Producer: creates copies of a resource.

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

Buffers: used to hold information after consumer has created it but before consumer has used it.

Synchronization: keeping producer and consumer in step.

Define constraints (definition of what is ``correct'').

Consumer must wait for producer to fill buffers. (scheduling)

Producer must wait for consumer to empty buffers, if all buffer space is in use. (scheduling)

Only one process must manipulate buffer pool at once. (mutual exclusion)

A separate semaphore is used for each constraint.

Initialization:

Put all buffers in pool of empties.

Initialize semaphores: emptiesAvl = numBuffers, fullsAvl= 0, mutex = 1;

Producer:

      P(emptiesAvl);
      P(mutex);
      get empty buffer from pool of empties;
      V(mutex);
      produce data in buffer;
      P(mutex);
      add full buffer to pool of fulls;
      V(mutex);
      V(fullsAvl);

Consumer:

      P(fullsAvl);
      P(mutex);
      get full buffer from pool of fulls;
      V(mutex);
      consume data in buffer;
      P(mutex);
      add empty buffer to pool of empties;
      V(mutex);
      V(emptiesAvl);

Important questions: