Shared restroom synchronization problem

Assume you own a bar that have single restroom with n stalls. In order to avoid lawsuits you want to make sure that only people of the same gender can be in the restroom at the same time and no accidents occur (nobody peed their pants). How would you write synchronization algorithm for it?

Translating into concurrency language we want to build a synchronization algorithm that allow no more than n threads of the same kind to enter critical section and it should be starvation free (threads that are trying to enter critical section eventually enter it).

The problem can be divided three parts:

How to allow only threads of the same kind to enter critical section

How to limit number of threads inside critical section to configured number

How to make the whole thing starvation free

Mutual exclusion algorithms have a good property with respect to starvation freedom. Assume you have two starvation free mutual algorithms A and B. Combined in the following way:

Enter code A

Enter code B

Critical section

Leave code B

Leave Code A

they form another starvation free mutual exclusion algorithm.

Limiting number of threads inside critical section to configured number can be easily solved with SemaphoreSlim (starvation free). Thus we need to solve problem of allowing only threads of the same kind to enter critical section.

Let’s denote two types of threads: black and white. Assume that thread tries to enter critical section. The following case are possible:

No other threads are in the critical section, so it can enter

There are threads of the same color in the critical section

no threads of the different color are waiting, so it can enter

there are waiting threads of the different color, so it cannot enter to prevent starvation of the waiting threads and must wait

There are threads of the different color in the critical section so it must wait

Now to prevent starvation we will switch turn to the different color once group of threads of current color leaves critical section. Turn is set to color that is now allowed to enter critical section. However if no threads are in the critical section a thread may enter if its not its turn (basically it captures the turn).

class WhiteBlackLock
{
private readonly object _sync = new object();
private readonly int[] _waiting = new int[2];
private int _turn;
private int _count;
public IDisposable EnterWhite()
{
return Enter(0);
}
public IDisposable EnterBlack()
{
return Enter(1);
}
private IDisposable Enter(int color)
{
lock (_sync)
{
if (_waiting[1 - _turn] == 0 &&
(_count == 0 || _turn == color))
{
// Nobody is waiting and either no one is in the
// critical section or this thread has the same
// color
_count++;
_turn = color;
}
else
{
// Either somebody is waiting to enter critical
// section or this thread has a different color
// than the ones already in the critical section
// and thus wait with the rest of the same color
_waiting[color]++;
// Wait until current group
while (_waiting[color] > 0)
Monitor.Wait(_sync);
}
// Wrap critical section leaving in a disposable to
// enable convenient use with using statement
return new Disposable(this);
}
}
private void Leave()
{
lock (_sync)
{
// Indicate completion
if (--_count != 0)
return;
// If this is the last one of the current group make
// way for threads of other color to run by switching
// turn
_turn = 1 - _turn;
// Before threads are awoken count must be set to
// waiting group size so that they can properly report
// their completion and not change turn too fast
_count = _waiting[_turn];
// Indicatet that current group can enter critical
// section
_waiting[_turn] = 0;
// Wake up wating threads
Monitor.PulseAll(_sync);
}
}
class Disposable : IDisposable
{
private readonly WhiteBlackLock _lock;
private int _disposed;
public Disposable(WhiteBlackLock @lock)
{
_lock = @lock;
}
public void Dispose()
{
// Make sure only the first call of allowed multiple
// calls leaves critical section
if (Interlocked.Exchange(ref _disposed, 1) == 0)
_lock.Leave();
}
}
}

In order to avoid lock ownership tracking leaving critical section is represented through disposable.