Chapter 17 Resource Allocation and Deadlock

Exercises

17-1 Consider a wide range of examples of objects or resources that are allocated by a system to meet the demands of the application or applications that it runs. Consider the extent to which these demands are predictable in advance. Consider real-time systems, multi-user (distributed) operating systems, database management systems etc.

This chapter focuses on algorithms that can be used to solve general resource allocation problems. There are often simpler solutions to specific concurrent programming problems, such as the dining philosophers problem. Other large concurrent programs, with many locks, semaphores or monitors, for example, will require a more general approach.

The subject is often discussed for the resources allocated dynamically by operating systems such as certain devices, disk space, memory and so on. In batch systems a "job" had to specify its resource requirements as part of the job control information. In interactive systems information is passed from compiler to linking loader to OS to indicate some of the resource requirements of a program that is to run, for example the amount of memory it will need.

In database systems the resources that are allocated dynamically are the data objects stored by the system.

17-2 What are deadlock, livelock and starvation? Give examples.

Deadlock implies that a blocked cycle of processes exists. Each holds at least one resource and is requesting another resource which is held by another process belonging to the cycle. Chapter 17 gives some examples.

Livelock implies that a process is not blocked but that it will never progress to its next stage. An example is busy waiting on a flag that no other process will ever change. Another is an incorrect entry protocol for a critical region where the processes execute the protocol indefinitely but none enters the region.

Starvation implies that a process is being unfairly treated with respect to other processes. A process may be blocked waiting for a resource that, for some reason, is always given to some other process.

17-3 What are the four conditions that must hold for deadlock to exist in an object allocation system? Which of these are system policies? Which is concerned with a specific sequence of events that happens to have occurred?

Is it possible to ensure that any of these conditions can be guaranteed not to hold?

This is reinforcing the material presented in 17.4. It is not always possible, depending on the application. For some applications one can ensure that one of the conditions does not hold.

17-4 Consider the following alternative specifications of the resource requirements of a process. In each case the process makes requests dynamically for the objects it needs.

a) No resource requirements are specified in advance.

b) The total resource requirements are specified.

c) The order in which the resources are required is specified.

d) The order in which resources are acquired and released is specified.

Discuss how these levels of information on resource requirements could be used to handle the possibility of deadlock. What are the tradeoffs involved in deciding on the amount of information to hold and the amount of processing to be done on it.

a) We can only detect deadlock.

b) We can avoid deadlock.

c) If this is a system-defined order so that all processes acquire their resources in the same order we have prevented deadlock by making a cycle impossible.

d) First assume a static number of processes such as in a process control system. It is possible to define a total schedule for the processes comprising steps or phases such that a process moves to a new phase when it releases a resource or requires a resource. A system state comprises a phase of each process. An algorithm is desired to take the system from its starting state to its finishing state (or the end of a period after which it restarts) without entering an unsafe state.

17-5 Devise the following solutions to the dining philosophers problem of Section 17.4:

a) Take the semaphore program given in Section 17.4 as a starting point.

Explore the use of an additional semaphore to achieve mutual exclusion, either to ensure that both forks are picked up at once or to simulate a room which the philosophers enter one-at-a-time to eat. Adapt this latter solution to allow four philosophers at once to enter the room.

b) Write semaphore programs which break the symmetry. Let odd-numbered philosophers pick up their forks left then right and even numbered philosophers right then left. An alternative is that just one philosopher picks up his forks in the opposite order.

c) Write the monitor "solution" that is equivalent to our first attempt, given in Section 17.4, that is susceptible to deadlock.

d) Write a monitor solution that simulates a room in which the philosophers eat one-at-a-time.

e) Write a monitor solution that allocates forks so that only four philosophers at once may be attempting to eat.

f) Write a monitor solution such that philosophers request both forks at once.

g) The above solutions are specific to this problem. Explore the use of the general approaches to deadlock detection and avoidance described in this chapter. For example, a fork-allocation monitor could run a deadlock detection algorithm whenever it could not satisfy a request for a fork. Or each process might register a claim for the total resources it might ever require before starting to run its algorithm. A fork allocation monitor might then run a deadlock avoidance algorithm. Note the length of these solutions.

The problem is a classic in the literature on concurrent programming. Although it is intended primarily to point out the dangers of writing symmetric solutions for concurrent processes (problems may arise if each process i executes the same program) we shall also use it to illustrate the general approach to deadlock detection and avoidance given in Chapter 17. We first give some solutions to the specific problem using semaphores and monitors.

The following variables are shared by all processes:

varfork: array (0..4) of semaphore % initialised to (1, 1, 1, 1, 1)

We use @+ to indicate addition modulo 5, where the processes have id’s 0..4.

A first attempt at a solution is for each process (identifier i) to execute the following program:

repeat

think

WAIT (fork(i));

WAIT (fork(i@+1));

eat

SIGNAL (fork(i));

SIGNAL (fork(i@+1))

until false;

The problem is that the processes might run concurrently in strict synchronisation so that each succeeds in picking up its left fork then requests its right fork. We then have deadlock.

A solution is to use an additional semaphore to achieve exclusive execution of the two WAIT operations:

guard: semaphore := 1

repeat

think

WAIT (guard); % put a critical region round

WAIT (fork(i)); % the two WAIT (fork) operations

WAIT (fork(i@+1));

SIGNAL (guard);

eat

SIGNAL (fork(i));

SIGNAL (fork(i @+ 1))

until false;

We can invent stories about the philosophers entering the room with the table one at a time in order to eat, in which case we would put SIGNAL (guard) after the two SIGNAL(fork) operations in the above solution instead of before eat, so that the philosophers eat in the room, put down their forks and leave it. Carriero and Gelernter (1989) give this solution in terms of the Linda primitives (Section 12.9.2).

Although this solution avoids the problem of deadlock it is unnecessarily conservative in restricting concurrency. We could allow some combinations of philosophers to eat together, for example, 0 can eat with 2 or 3 but not with 1 or 4. If we allow four processes to enter the room at once, any one might be delayed, waiting for a fork, but would eventually be able to eat:

count: semaphore := 4

repeat forever

think

WAIT (count); % only 4 at once can pass this point

WAIT (fork(i));

WAIT (fork(i@+1));

eat

SIGNAL (fork(i));

SIGNAL (fork(i@+1));

SIGNAL (count); until false;

If we break the symmetry of the processes’ programs we can avoid the possibility of a cycle of deadlocked processes. We can do this by making one process acquire its forks in the opposite order to the others, or we can make processes with odd identifiers pick up the forks in one order and even ones in the other order:

if odd(i) then....else.....;

The semaphore WAIT operation does not allow us to explore solutions where we release the first fork if the second is already in use.

Let us now move on to monitor solutions to the problem. Going back to our first attempt, a similar monitor "solution" is:

fork-allocator: monitor

var fork:array (0..4) of integer range 0..1 % initialised to (1, 1, 1, 1, 1)

fork-free: array (0..4) of condition; procedure acquire-fork (i)

if fork(i)=0 then WAIT (fork-free (i)); fork(i) :=0;

end acquire-fork (i);

procedure release-fork (i)

fork(i) :=1;

SIGNAL (fork-free (i))

end acquire-fork (i); end fork-allocator;

And each process (i) executes:

repeat

think

fork-allocator. acquire-fork(i);

fork-allocator. acquire-fork(i@+1);

eat

fork-allocator. release-fork(i);

fork-allocator. release-fork(i@+1) until false;

Our solution which allows only one process at once into the room can be rewritten with a monitor representing the room:

room-1: monitor

var fork:array (0..4) of integer range 0..1 % initialised to (1, 1, 1, 1, 1)

procedure eat (i)

fork(i):=0; fork(i@+1):=0; eat; fork(i) :=1; fork(i@+1):=1;

end eat (i) end room-1;

And a philosopher executes:

repeat

think; room-1.eat(i);

until false;

The monitor shows the degree to which concurrency is restricted more clearly than the semaphore solution. The possibility of contention for resources is removed.

If we allow four at a time to eat we cannot put eat inside a monitor. We have to make our story include a means for philosophers to acquire permission to start eating.

eat-4: monitor var fork:array (0..4) of integer range 0..1 % initialised to (1, 1, 1, 1, 1)

fork-free: array (0..4) of condition; eat: condition; count: integer :=4;

procedure start-eat(i)

count:=count-1; if count=0 then WAIT (eat); % are four already attempting to eat? if fork(i)=0 then WAIT (fork-free (i)); fork(i) :=0; if fork(i@+1)=0 then WAIT (fork-free (i@+1)); fork(i@+1) :=0;

end acquire-fork (i);

procedure end-eat(i)

fork(i) :=1; fork (i@+1)=1

SIGNAL (fork-free (i)); SIGNAL (fork-free (i@+1)); count := count+1; SIGNAL (eat) end end-eat(i);

end eat-4;

And each process (i) executes:

repeat

think

eat-4.start-eat(i);

eat

eat-4.end-eat(i)

until false;

Other monitor solutions are possible. Students may be asked to devise a monitor solution which causes a process to free its left fork if its right fork is busy and one which allows a process to acquire both forks in one call to a monitor procedure acquire-forks (i).

Notice that the monitors which avoid the possibility of deadlock are written to solve this specific problem. In the monitor fork-allocation, where deadlock is possible, there is no knowledge of the resource requirements of the processes in the monitor. It attempts to satisfy each request that arrives and blocks a process if its resource request cannot be satisfied.

fork-allocation could be extended to run a deadlock detection algorithm when it cannot satisfy a resource request. The array fork can be expanded into an allocation matrix and a request matrix request (0..4,0..4) must also be maintained. It is therefore necessary for the process id as well as the required fork id to be passed as an argument to the procedure acquire-fork. The pseudo-code below gives the general idea. The code is becoming long because we are taking the general approach of Section 17.6 to solving the problem.

procedure acquire-fork (j,i) % process j wants fork i;

if fork(i)=1 then fork(i) :=0

else begin request (j,i) :=1;

deadlock:boolean := deadlock-detect();

if deadlock then "raise exception"

else begin WAIT (fork-free (i));

fork(i):=0 end end end acquire-fork (i);

We can extend this general approach by making information available within the monitor on the total resource requirements of each process. This might be done statically or, more generally, to require a process to register its total requirements at the monitor before claiming any resource. The general deadlock prevention algorithm could then be run before any request was granted. Again, the program is becoming very long. For example, when processes 0,1,2 and 3 have each acquired one fork and process 4 requests a fork we have:

process allocated forks requested forks total required

0 (1, 0, 0, 0, 0) (0, 0, 0, 0, 0) (1, 1, 0, 0, 0)

1 (0, 1, 0, 0, 0) (0, 0, 0, 0, 0) (0, 1, 1, 0, 0)

2 (0, 0, 1, 0, 0) (0, 0, 0, 0, 0) (0, 0, 1, 1, 0)

3 (0, 0, 0, 1, 0) (0, 0, 0, 0, 0) (0, 0, 0, 1, 1)

4 (0, 0, 0, 0, 0) (0, 0, 0, 0, 1) (1, 0, 0, 0, 1)

available forks (0, 0, 0, 0, 1)

The deadlock avoidance algorithm of Section 17.7 could be run on this data and would show that it is not safe to grant this request.

This example shows the students the general algorithms discussed in Chapter 17 in the context of a simple, specific example.