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:
Semaphore: A synchronization variable that takes on positive integer values. Invented by Dijkstra.
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 | |
1 2 3 |
OKToBuyMilk.P(); if (NoMilk) BuyMilk(); OKToBuyMilk.V(); |
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.
semaphore empty(N), full(0), list(1); List empties (N), fulls (0);
empty.P(); list.P(); b = empties.remove(); list.V(); Put data into B list.P(); fulls.add(b); list.V(); full.V();
full.P(); list.P(); b = fulls.remove(); list.V(); Remove data from B consume data in buffer; list.P(); empties.add(b); list.V(); empty.V();
Producers and consumers are much like Unix pipes.
THIS IS A VERY IMPORTANT EXAMPLE!