One approach to dealing with the critical section problem is the use of a facility first proposed by Dijkstra called a semaphore.
In its simplest form, a semaphore is a boolean variable with two indivisible hardware operations possible on it:
P(s) | wait(s) | while s = 0 do no-op; s := 0 |
V(s) | signal(s) | s := 1 |
Such a semaphore could be implemented easily using any of the hardware facilities described above - e.g. with test and set (equating 0 to true and 1 to false!):
P(s) | wait(s) | while test-and-set(s) do no-op; |
V(s) | signal(s) | s := 0 |
P(s) | wait(s) | 1$: BBCCI S, 1$ |
V(s) | signal(s) | 2$: BBSSI S, 2$ 2$: ... |
In practice, we often use one of several possible generalizations of the basic semaphore.
One such generalization is called a counting semaphore, which can assume any (non-negative) integer value. The operations - which must be done indivisibly - are:
P(s) | wait(s) | while s <= 0 do no-op; s := s - 1 |
V(s) | signal(s) | s := s + 1 |
Note that the binary semaphore is a special case, with s constrained to assume only the values 0,1.
Counting semaphores are a bit more complex to implement. We might try to use an add-interlocked:
P(s) | wait(s) | 1:  add-interlocked(s, -1);      if result < 0 then begin           add-interlocked(s, 1);           goto 1;      end; |
V(s) | signal(s) | add-interlocked(s, 1) |
A counting semaphore could be implemented, however, using a second, binary semaphore (called here s'):
P(s) | wait(s) | repeat      P(s');      temp := s;      if s > 0 then s := s - 1;      V(s'); until temp > 0; |
V(s) | signal(s) | P(s'); s := s + 1; V(s'); |
A further improvement is to associate a queue with a semaphore, so that we have:
P(s) | wait(s) | s := s - 1; if s < 0 then block process in a queue on s |
V(s) | signal(s) | s := s + 1; if s <= 0 then unblock one queued process |
Note: in the literature, it is common to find that no assumptions are made about the behavior of the queue - i.e. it is not necessarily a true, FIFO queue.
The semaphore with a queue associated with it is the variant we will use in our examples.
How do we implement a semaphore with a queue? We treat it as a critical section in its own right, with its mutual exclusion guaranteed by a lower level method such as a binary semaphore or one of the software schemes. (Here the busy waiting is very short and can be tolerated.)
Examples of using semaphores to solve classical problems:
Bounded buffer - TEXT (5th ed, pages 172-173).
Readers/writers - TEXT (5th ed, pages 173-175). Note that we have to decide whether to let writers starve if there is a continual flow of readers (the so called first-readers/writers problem), or readers starve if there is a continual flow of writers (second form of the problem).
Dining philosophers - TEXT (5th ed, pages 175-177). Note possibility of deadlock but not starvation. (We'll investigate deadlock-free solutions later.)
As an aside, we also note another use for semaphores. They can be used with PARBEGIN .. PAREND to enable any precedence graph to be realized.
Example: Our earlier precedence graph:
S1 | \ S2 S3 | / \ S4 S5 \ / S6 S1; -- all semaphores initially 0 PARBEGIN BEGIN S2; V(a); END; -- semaphore a guarantees S2 precedes S4 BEGIN S3; V(b); V(c); END; -- semaphores b, c guarantee that S3 BEGIN P(a); P(b); S4; END; -- precedes both S4 and S5 BEGIN P(c); S5; END; PAREND; S6;
Problems with the semaphore solution to the critical-section problem:
The burden is on the programmer to use the semaphore. There is no way to stop a programmer from updating a shared variable without first doing the necessary P(), short of manually inspecting all code. Thus, mutual exclusion can be lost through carelessness or laziness.
Subtle errors are easily made, and can lead to big problems. E.g. suppose a particular critical region needs access to data protected by two different semaphores S1 and S2. One process might code:
P(S1); P(S2); critical region; V(S1); V(S2);while the other could code this:
P(S2); P(S1); critical region; V(S2); V(S1);which could lead to deadlock if the two processes both did their first P() before either did its second.
$Id: concurrency6.html,v 1.3 1998/03/18 21:27:06 senning Exp $
These notes were written by Prof. R. Bjork of Gordon College. In February 1998 they were edited and converted into HTML by J. Senning of Gordon College.