void a() { Dllist d1, d2; d1 = allocate(6, 6, 6); sleep(10); d2 = allocate(1, 0, 0); sleep(10); release(d2); release(d1); } void b() { Dllist d1; sleep(1); d1 = allocate(5, 5, 5); sleep(10); release(d1); }Here's one that involves 20 resources in a circular wait:
void a() { Dllist d1, d2; d1 = allocate(6, 6, 6); sleep(10); d2 = allocate(1, 0, 0); sleep(10); release(d2); release(d1); } void b() { Dllist d1; sleep(1); d1 = allocate(4, 4, 5); sleep(10); release(d1); }And here's one that involves all 30 resources:
void a() { Dllist d1, d2; d1 = allocate(0, 0, 10); sleep(10); d2 = allocate(1, 0, 0); sleep(10); release(d2); release(d1); } void b() { Dllist d1; sleep(1); d1 = allocate(0, 10, 1); sleep(10); release(d1); } void c() { Dllist d1; sleep(2); d1 = allocate(10, 1, 0); sleep(10); release(d1); }Part 2
void a() { Dllist d1; d1 = allocate(0, 0, 5); sleep(5); release(d1); } void b() { Dllist d1; sleep(1); d1 = allocate(0, 0, 6): sleep(5); release(d1); } void c() { Dllist d1; sleep(2); d1 = allocate(0, 0, 6); sleep(5); release(d1); }Now, all three of these are forked off at the same time. a() grabs 5 disks and sleeps. Then b() will enter, grab 5 disks, and then block on the disk condition variable waiting for the 6th disk. Meanwhile, c() enters the system, and also blocks on the same condition variable (when b() blocks, it releases the mutex). a() now releases the 5 disks, and if c() wakes up from the wait() call first (remember no FIFO ordering is insured), then c() will grab 5 disks, and we have deadlock. Part 3
Since you can't have hold-and-wait when you hold a disk, you cannot have deadlock involving disks. This means we can remove disks from the equation.
The same logic now applies to tape drives. You can't have deadlock with disks, so the only potential deadlocks would involve tape drives and/or printers. However, once you've allocated a tape drive, you either return, or you hold it to allocate a disk. This means you will never hold a tape drive and wait for any resource that can be involved in a circular wait, which means you cannot have a deadlock where you hold a tape drive.
The same logic aplies to printers.
Note, this situation is very different from part 2 because there you can hold a disk and wait for a disk, and that's where circular wait is possible.
An alternative proof, which I liked was that whenever a resource is granted, the system is in a safe state. Why? Because all processes that own disks can finish and then release their resources. The all those that are waiting for disks can get them and finish. Then all those that own tape drives. Then all those that are waiting for tape drives. Then all those that own printers. Then all those that are waiting for printers. Cool, no? that own printers.
void a() { Dllist d1; d1 = allocate(1, 0, 0); sleep(50); release(d1); }Suppose you fork off one a() thread every second. After 50 seconds, there will be a steady state of 40 threads waiting on the condition variable. Each second an old thread releases a printer, and a waiting thread gets a printer. Since there is no ordering enforced on who wakes up from cv_wait(), it is entirely conceivable that a thread will get starved.
A bunch of you said that if you insert sleep calls between the get_resource() calls, you can deadlock. That's not true -- the ordering of the resources still ensures no circular wait, and adding sleep calls does not change anything (i.e. if one thread overtakes another, then it is the same as that thread having called allocate() before the other -- nothing is changed). If you gave that answer, you received one point.