Slides Used During Today's Lecture
Monitors, Philosophers, and Deadlock introduction(ppt)
Reading:
Chapter 8
A Monitor Question From Last Year's Midterm
In grading the homework, it appeared that more people than I liked were having difficulty with monitors. This isn't surprising since the monitors question on last year's midterm exam beat up more people than we would have liked. Since I'd like to avoid that situation again this year, I thought I'd take the early-warning that the homework provided and do another example.Let's take a look at last year's monitor question and produce solutions using all three flavors of monitors. Then, if you'd like, you can resubmit problem #5 of the homework -- I'll need your new submission by 4:30 on Monday so that I have time to grade it and hand it back.
3. [15 points] Fire? What fire?
Fire! The 15-412 students drop their exams and rush to evacuate via the only exterior doorway. Meanwhile, the valiant civil servants of the Pittsburgh Fire Bureau rush toward the same doorway to douse the fiery inferno. But alas, the celebrating students can't exit through the doorway at the same time the firefighters enter. Unless the situation is controlled, the firefighters will enjoy a picnic lunch outdoors and the students will become toast.You must model a solution to this terrible plight that allows the students to escape and enjoy their weekend and allows the firefighters to enter the building and prevent any more singed concrete.
Below are the rules that allow maximum use of the doorway, without deadlock:
Furthermore, your solution must ensure the following:
- There is only one doorway.
- The doorway can accommodate no more than one student at a time.
- The doorway can accommodate no more than two firefighters at a time.
- The doorway cannot be shared by students and firefighters at the same time.
- A student should not enter the doorway if a firefighter is currently trying to enter.
Your solution should use a single monitor as the tool for guaranteeing mutual exclusion and synchronization. Please state which semantics are required by the monitor used in your solution: Hoare, Mesa, or Brinch Hansen. Be sure to declare an initialize all shared variables.
- Firefighters cannot be starved.
- Students can only be starved by firefighters.
- There is no deadlock.
Your solution should have a single monitor with the following entry procedures:
A Mesa Semantics Solution
- FirefighterEnterDoorway
- FireFighterExitDoorway
- StudentEnterDoorway
- StudentExitDoorway
In order to solve this problem, we must begin by asking ourselves the question, "What is shared?" The answer to this question is the doorway -- out monitor must control access to the doorway.
What is the sharing discipline? It is not mututal exclusion. It is two firefighters or one student, such that firefighters can starve students, but not vice versa.
To do this we can envision a system where students and firefighters get in line in front of the doorway. If the doorway is empty, the arriving person goes right into it. Otherwise he/she gets into the right line. Upon exiting the doorway, the person signals a person or two people to enter, as appropriate.
MESA MONITOR doorway { int firefighterInDoorway = 0; int firefighterWaiting = 0; int studentInDoorway = 0; condition student, firefighter; Entry FirefighterEnterDoorway() { fireFighterWaiting++; while ( (studentInDoorway) || (firefighterInDoorway >= 2) ) firefighter.wait(); firefighterInDoorway++; firefighterWaiting--; } Entry FirefighterExitDoorway() { firefighterInDoorway--; if (firefighterWaiting()) firefighter.signal(); else if (!firefighterInDoorway) student.signal(); } Entry StudentEnterDoorway() { while ( (studentInDoorway) || (firefighterInDoorway) (firefighterWaiting) ) student.wait(); studentInDoorway++; } Entry StudentExitDoorway() { studentInDoorway--; if (firefighterWaiting) { firefighter.signal(); firefighter.signal(); } else student.signal(); } }A Hoare Semantics SolutionIn order to convert the Mesa semantics solution into something that functions with Hoare semantics, we must look at two things:
In this case, we can change the while's to ifs (whiles are still safe), and the code after signals is safe. The only time we do something after signalling is when we signal twice in a row. In that case the number of firefighters in the doorway could have changed from 0 to 1. But even if it is now 1, we're still safe -- it could not have gone to 2.
- We don't need to loop around the predicates (conditions) for which we are waiting -- no one can run between the time that we are signalled and the time that we run. The whiles can become ifs.
- We need to check the code after ever time we signal and ask ourselves if it is still safe -- we need to remember that we could be preempted before it runs. Are all of the resources still available? In the state that we expect?
HOARE MONITOR doorway { int firefighterInDoorway = 0; int firefighterWaiting = 0; int studentInDoorway = 0; condition student, firefighter; Entry FirefighterEnterDoorway() { firefighterWaiting++; if ( (studentInDoorway) || (firefighterInDoorway >= 2) ) firefighter.wait(); firefighterInDoorway++; firefighterWaiting--; } Entry FirefighterExitDoorway() { firefighterInDoorway--; if (firefighterWaiting()) firefighter.signal(); else if (!firefighterInDoorway) student.signal(); } Entry StudentEnterDoorway() { if ( (studentInDoorway) || (firefighterInDoorway) (firefighterWaiting) ) student.wait(); studentInDoorway++; } Entry StudentExitDoorway() { studentInDoorway--; if (firefighterWaiting) { firefighter.signal(); firefighter.signal(); } else student.signal(); } }A Brinch Hanson Semantics SolutionWhen we convert from Hoare sematnics to BH semantics, we have to convert ever signal() operation to a signalAndExit() operation. This means that any code after a signal has to be moved somewhere else.
In the case of the previous solution, this affects us in one critical place -- we can no longer signal twice in a row in order to let two firefighters into the doorway. Instead we convert the solution so that we signal the first firefighter. Firefighters must become more considerate -- if a firefighter enters the doorway and there is room for another, she/he signals another firefighter to enter. Of course, we need to be careful that this signal is the last thing that the firefighter does within the monitor -- as before, the operation is signalAndExit().
BH MONITOR doorway { int firefighterInDoorway = 0; int firefighterWaiting = 0; int studentInDoorway = 0; condition student, firefighter; Entry FirefighterEnterDoorway() { firefighterWaiting++; if ( (studentInDoorway) || (firefighterInDoorway >= 2) ) firefighter.wait(); firefighterInDoorway++; firefighterWaiting--; if ((firefighterInDoorway < 2) && (firefighterWaiting) ) firefighter.signalAndExit(); } Entry FirefighterExitDoorway() { firefighterInDoorway--; if (firefighterWaiting()) firefighter.signal(); else if (!firefighterInDoorway) student.signalAndExit(); } Entry StudentEnterDoorway() { if ( (studentInDoorway) || (firefighterInDoorway) (firefighterWaiting) ) student.wait(); studentInDoorway++; } Entry StudentExitDoorway() { studentInDoorway--; if (firefighterWaiting) { firefighter.signalAndExit(); } else student.signalAndExit(); } }
Dining Philosophers
The problem:
Rules
- Philosophers alternate between thinking and eating
- 2 chopsticks are required to eat
- Philsophers are dignified and never grab with both hands -- they pick up the chopsticks one at a time
- A philospher is too polite to steal a chopstick from a colleague
- The philosophers cannot be allowed to starve
- More than one philosopher must be able to eat at a time.
Approach #1
#define left(i) (i)
#define right(i) ((i-1) % 5)
MONITOR fork {
int avail[5] = {2, 2, 2, 2, 2 }; /* forks available to each phil */
condition hungry[5];
entry pickup_fork (int phil) {
if (avail[i] != 2) hungry[i].wait();
avail[left(I)]--; avail[right(I)]--;
}
entry putdown_fork (int phil) {
avail[left(i)]++: avail[right(i)]++;
if (avail[left(i)] == 2) hungry[left(i).signal();
if (avail[right(i)] == 2) hungry[right(i)].signal();
}
}
Starvation: Why?
This approach fails due to a very common concern -- starvation. There is no guarantee that an individual philospher would ever be able to eat.
In general, starvation occurs because different parties are waiting for different conditions with no common way to select among them. The solution is very straight-forward. All parties should wait for the same condition. An easy fix might be a common queue for waiting processes.
Another Approach
This time let's look at a sempahore-based approach (I like to mix it up so that we don't play favorites).
Semaphore chopstick[5] = { 1, 1, 1, 1, 1 };
while (1)
{
P(chopstick[i]);
P(chopstick[(i+1) % 5]);
<<< eat >>>
V(chopstick[i]);
V(chopstick[(i +1) % 5]);
<<< think >>>
}
But this solution isn't right either! What can happen? It is possible that the philosophers won't cooperate resulting in no food for anyone -- consider what would happen if each philosopher grabbed his/her left chopstick. No one would be able to eat, because everyone would be waiting for someone else to put down his/her chopstick first. This is called deadlock.
What is Deadlock?
First, let's define a resource:
- Resources are an abstraction of any reason to wait.
- Resources come in different types and we can have different numbers of each type.
- A process/thread can acquire a resource, use it, and then fee it.
- In this context, resources are unshareable - they are serially reusable.
Now, let's formally define deadlock:
- The condition that arises when there exists a set of processes (or threads) such that each process holds a resource that another process in the set is waiting to acquire. The situation forces all processes in the set to wait forever.
Why Does Deadlock Occur?
Deadlock occurs if these conditions are satisfied:
- Mutual exclusion -- at least one resource must be held by a process.
- Hold and wait -- at least one process hold a resource while it is waiting for another resource.
- No preemption -- one process can't take another process's resources in order to make progress (nor can the OS)
- Circular wait -- there exists a circular chain of processes, each of which is waiting for a resource held by the next process in the chain.
Simple Defense: Serialization
This attacks the circular wait condition.
- One simple defense against deadlock is to serialize the request of resources.
- Enumerate all of the resources, giving each a number.
- Require that all processes request resources in the order of this enumeration. That is to say that they are designed so that they never request a resource with a lower number than the highest numbered resource that they hold.
- Circular wait is now impossible, because the chain of waiting cannot wrap around from the greatest back to the beginning. It will
Other Defenses
It is possible to attack the other three conditions as well -- but this is often trickier:
- Mutual exclusion -- if you can, make the resource sharable, ex. read-only files or pages of memory
- Hold and wait -- if you can't get what you want, release all your resources and try again (not bounded wait), or simply die!
- No preemption -- give the OS (or another process) a stick and let it take away resources. Perhaps it can kill the process, or perhaps it can put the process to sleep before the surgery.
Dining Philosophers -- Serialized
If we enumerate the chopsticks and for each philosopher to request the lowest number chopstick first, deadlock cannot happen. This is the same as each philosopher picking up the chopstick on the left -- except for P4. She/he will pick up the right chopstick first, since it has a lower number -- this breaks circular wait. Notice that there isn't a cycle of dependency.
![]()
Semaphore chopstick[5] = { 1, 1, 1, 1, 1 }; while (1) { if (i < ((i+1) % 5)) { P(chopstick[i]); P(chopstick[(i+1) % 5]); } else { P(chopstick[(i+1) % 5]); P (chopstick[i]); } <<< eat >>> V(chopstick[i]); V(chopstick[(i +1) % 5]); <<< think >>> }