The Potty Parity Problem

(Computer science has a funny tradition of posing silly little stories as examples of synchronization problems that crop up in concurrent systems. See also the Dining Philosophers, the Dating Philosophers, the Sleeping Barber, and the Part-Time Parliament of Paxos.)

There exists a single public restroom. There may be multiple men in the restroom, or multiple women, but not both simultaneously. Write a monitor representing the restroom that safely controls access to the system. Assume that each person in the system is represented by a thread that calls Restroom.enter before entering and Restroom.exit before exiting.

To make the problem harder, apply one or both of these additional conditions:

  1. There may be a maximum of N people in the room at once.
  2. Avoid making any one person wait indefinitely.
The following is a correct solution to the simple problem in Java:
class Restroom {
	public static final int MALE = 0;
	public static final int FEMALE = 1;

	private int menInside = 0;
	private int womenInside = 0;

	public synchronized void enter( gender g ) {
		if(g==MALE) {
			while(womenInside>0) wait();
			menInside++;
		} else {
			while(menInside>0) wait();
			womenInside++;
		}	
	}

	public synchronized void exit( gender g ) {
		if(g==MALE) {
			menInside--;
		} else {
			womenInside--;
		}
		notifyAll();
	}
}
These sorts of programs get complicated rather quickly, so let's simplify it to an equivalent program, using an array to represent menInside and womenInside:
class Restroom {
	public static final int MALE = 0;
	public static final int FEMALE = 1;

	private static int numInside[] = new int[2];

	public synchronized void enter( int gender ) {
	       while(numInside[1-gender]>0) wait();
	       numInside[gender]++;
	}

	public synchronized void exit( int gender ) {
	       numInside[gender]--;
	       notifyAll();
	}
}
Now, let's try to solve one or both of the harder conditions listed above. The following code is an incorrect solution to the limited capacity problem. Study the code, find the problem, and fix it. (Don't worry about pointless Java trivia like InterruptedException.)
/** INCORRECT SOLUTION TO LIMITED CAPACITY **/

class Restroom {
	public static final int MALE = 0;
	public static final int FEMALE = 1;

	public static final int CAPACITY = ... ;

	private static int numInside[] = new int[2];

	public synchronized void enter( int gender ) {
	       	while(numInside[1-gender]>0) wait();
		while(numInside[gender]>=CAPACITY) wait();
		numInside[gender]++;
	}

	public synchronized void exit( int gender ) {
		numInside[gender]--;
		notifyAll();
	}
}
The following code is an incorrect solution to the starvation problem. It adds numWaiting to keep track of how many people of each gender are waiting, and then (politely) asks people to wait if a line of the opposite gender is forming. What could possibly go wrong?
/** INCORRECT SOLUTION TO STARVATION **/

class Restroom {
	public static final int MALE = 0;
	public static final int FEMALE = 1;

	private static int numInside[] = new int[2];
	private static int numWaiting[] = new int[2];

	public synchronized void enter( int gender ) {
		while( numInside[1-gender]>0 || numWaiting[1-gender]>0 ) {
			numWaiting[gender]++;
			wait();
			numWaiting[gender]--;
		}
		numInside[gender]++;
	}

	public synchronized void exit( int gender ) {
		numInside[gender]--;
		notifyAll();
	}
}
Programming concurrent systems can be surprisingly subtle, because you must think very carefully about all of the possible configurations that can arise. Perhaps you found the problems in the code above by trying out some situations step-by-step. (A man arrives, then a woman, then a man, then a woman, oops!)

A famous bit of advice from Edsger Djikstra was that we need to think about such problems rigorously by carefully addressing two conditions:

  • Safety - Would proceeding result in a safe situation, according to the strict definition of the problem?
  • Liveness - Does the situation make forward progress wherever possible?
  • Define the safety and liveness conditions for the Potty Parity problem, and evaluate the solutions above against those conditions. (Hint: keep in mind that when a thread calls wait, many other threads will have the opportunity to run, and previous assumptions may no longer hold.)