Lecture 20

25 November 1998


Created 25/11/1998

More on Conditional Critical Regions

The question is: how to implement

region x when B do S;

using semaphores?

Would this one do?

	while not B do ;
	wait(mutex);
	S;
	signal(mutex);

How about this one?

	wait(mutex);
	while not B do
	begin
		signal(mutex);
		wait(mutex);
	end;
	S;
	signal(mutex);

But the busy loop is just too tight, wasting a lot of CPU cycles. This one is more like it:

	wait(mutex);
	while not B do
	begin
		signal(mutex);
		delay;
		wait(mutex);
	end;
	S;
	signal(mutex);
where delay is inserted to put the process to sleep temporarily, until preferably the time when there is a possibility that B could have become true for the process. This serves as a skeleton for the solutions we describe below.

Three-Semaphore Solution

Refer to Page 181 of the textbook, where Fig. 6.18 contains an implementation of conditional critical region (CCR) using three binary semaphores.

The following is a graphical representation of that implementation.

Note that B could be different for different processes, as is the case of the bounded buffer problem:

	producer: ... region buffer when count < n ...
	consumer: ... region buffer when count > 0 ...
The B of a delayed process (because B is presently false) can become true only as the result of some other process executing S and changing some shared variable.

There are three queues, guarded by three different binary semaphores:

The entry gate is open if and only if

When some process is executing S, all other activities inside the region come to a standstill, and no processes are allowed to enter the region through the entry gate.

When the process finishes executing S, it initiates a transfer of all processes in Q1 to Q2. This transfer happens in a cascading fashion: the first process in Q1 is awaken, which awakens the next, and so on until all processes in Q1 have been awakened and transferred to Q2. Then one process in Q2 is allowed to reevaluate its B. If this process' B is true, it proceeds to execute S; otherwise, it rejoins Q1 and lets the second process in Q2 to reevaluate its B, and so on.

Only when Q2 becomes empty (and no process is executing S) would the entry gate be reopened, letting in new processes.

The three semaphores, mutex, first-delay, second-delay, are called split binary semaphores as they satisfy the following invariant property.

0 <= mutex + first-delay + second-delay <= 1

In other words, at most one of the three black dots in the picture above would turn "green" (=1) at any one time.

Can we use just one queue instead of two inside the region? The answer seems to be no, because if a process in Q2 immediately rejoins Q2 after reevaluating its B to be false, then we will be stuck in an infinite cycle of reevaluation by processes in Q2.

Two-Semaphore Solution Revisited

But if we keep track of how many processes there are in Q2, then we can stop after letting each one reevaluate its B. This is exactly the strategy adopted in the solution using two binary semaphores which I presented on 18/11 (borrowed from the OS text by Bic and Shaw):
	// entry:
        wait(mutex);
        delayCnt++;
        while (not B) {
                signal(mutex);
                wait(delay);
                wait(mutex);
        }
        delayCnt--;

        S;

        // exit:
        for (i = 1; i <= delayCnt; i++)
                signal(delay);
        signal(mutex);
where mutex, delay, and delayCnt are initialized to 1, 0, and 0, respectively.

The delay queue here is equivalent to Q1 in the previous solution: it holds processes waiting for their B to be changed (possibly) by some process executing S. Once the latter happens and completes, these processes (all of them) are transferred to the mutex queue so that they can reevaluate their B.

The above code has a little bug: the for line should read as follows since all the processes in the delay queue would be transferred.

	for ( ; delayCnt > 0; delayCnt--)
		signal(delay);

(This morning in class I spotted another bug: processes waking up from the delay will join the mutex queue, but they are not counted with delayCnt in the solution as shown above. These are big authors in the field, but see how careless they had been. The corrected code follows.)

	// entry:
        wait(mutex);
        while (not B) {
		delayCnt++;
                signal(mutex);
                wait(delay);
                wait(mutex);
        }

        S;

        // exit:
	for ( ; delayCnt > 0; delayCnt--)
		signal(delay);
        signal(mutex);

Here is the situation of this simpler solution:

One obvious difference between this solution and the previous one is that in the current solution, those transferred processes do not get to evaluate their B right away; they have to rejoin the mutex queue at the entry and wait for their turn to evaluate B again; whereas in the previous solution, processes transferred to Q2 immediately get to reevaluate their B albeit one after another.

A Bit of History

CCR was first proposed by Tony Hoare, a British professor, in 1972. The first published implementation of CCR was by H.A. Schmid in 1976. More widely known is Rem's algorithm, which was designed by Martin Rem in 1977. The three-semaphore solution above is exactly the Rem's algorithm. Two additional implementations, which use only two binary semaphores, were found by Kessels and Martin in 1979.