UNIVERSITY OF WISCONSIN-MADISON
Computer Sciences Department
CS 537
Spring 2001
A. Arpaci-Dusseau
Quiz #3: Feb 21th -- Synchronization
Name: Solutions Student ID #: Solutions

Problem 1: 88 points

The following Java code samples describe a Lock class two methods: acquire() and release(). You can assume that the application calls lock.acquire() before entering a critical section and lock.release() after exiting the critical section. For the implementations that require a tid (i.e., thread id), you can assume that the tid of each thread is either 0 or 1. Hint: Remember to consider how variables are initialized!

For each code sample, answer the following questions:

  1. Does the code guarantee mutual exclusion? (Simply answer yes or no)
  2. Does the code guarantee progress? (Simply answer yes or no)
  3. List all other limitations that exist for each implementation. Issues you might consider include (but are not limited to) the following: generality, efficiency, and fairness. (Note: You can skip this part of the question when the implementation fails to provide mutual exclusion or progress.)

class Lock {

    private int turn = 0;

    public void acquire(int tid) {
        while (turn == (1 - tid));
    }
    public void release(int tid) {
        turn = (1 - tid);
    }
}
  1. Yes, guarantees mutual exclusion; only the thread with tid matching turn is able to acquire the lock.
  2. No, does not guarantee progress; if thread 0 never tries to acquire the lock (it is executing other code), thread 1 will not be able to acquire the lock.
  3. Limitations (Not required): Only works with two processes, uses busy waiting.
class Lock {

    public void acquire() {
       disableInterrupts();
    }
    public void release() { 
       enableInterrupts();	
    }
}
  1. Guarantees mutual exclusion on a uniprocessor, but not on a multiprocessor. On a uniprocessor, once the timer interrupt is disabled, the scheduler won't be able to switch to another process (assuming the scheduled process doesn't voluntarily relinquish the CPU). However, on a multiprocessor, it is possible for the other CPU to be running a process that also acquires the lock.
  2. Yes, guarantees progress; once a process is scheduled, it is able to acquire the lock without incident.
  3. Limitations: Only works on uniprocessors; allows user processes to disable interrupts for an arbitrary long period; cannot service other important interrupts during critical section (e.g., I/O); cannot schedule any other processes when lock is held, even those not contending for the lock.
class Lock {

    private int turn = 0;
    private boolean lock[2] = {false, false};

    public void acquire(int tid) { 
        lock[tid] = true;
        turn = 1 - tid;
        while (lock[1-tid] && turn == (1 - tid));
    }

    public void release(int tid) { 
        lock[tid] = false;
    }
}
This is the solution presented in class.
  1. Yes, guarantees mutual exclusion.
  2. Yes, guarantees progress.
  3. Limitations: Only works for two threads; involves busy-waiting.
class Lock {

    private boolean lock = true;

    public void acquire() {
        while (TestAndSet(lock, true);
    }
    public void release() { 
        lock = false;
    }
}
Note that the lock was initialized to the wrong value.
  1. Yes, guarantees mutual exclusion. It is not possible for more than one process to acquire the lock.
  2. No, does not guarantee progress. No process is able to acquire the lock, since it is initialized to true, designating that the lock is already acquired.
  3. Limitations: Not needed.

Problem 2: 12 points

Locks are often implemented by maintaining a list of processes (or threads) that are waiting to acquire the lock. What are all of the advantages of this approach (compared to a correct implementation of a lock that does not use a list)? Be precise in your answer.

Advantages of maintaining a list of waiting processes include:

  1. Processes are moved to a blocked state and do not contend for the CPU. Therefore, they do not busy-wait and useful work can be performed.
  2. We can ensure that processes do not starve while waiting to acquire the lock; that is, each waiting process is eventually scheduled, regardless of how many new processes are added to the list.
  3. We can order the processes on the list according to any policy we choose. One reasonable policy is to allow higher priority processes to acquire the lock first; another is to implement a fair policy with a FIFO queue, where the process that has been waiting the longest is given the lock next.