The next problem in the “The Little Book of Semaphores” is called the “unisex bathroom problem”, by Allen Downey himself. The problem statement reads as follows:
A friend of the author took a job at Xerox. She was working in a cubicle in the basement of a concrete monolith, and the nearest women’s bathroom was two floors up. She proposed to the Uberboss that they convert the men’s bathroom on her floor to a unisex bathroom, sort of like on Ally McBeal.
The Uberboss agreed, provided that the following synchronization constraints can be maintained:
• There cannot be men and women in the bathroom at the same time.
• There should never be more than three employees squandering company time in the bathroom.
It’s a categorical exclusion problem: the limited resource is the bathroom and the exclusion is that any number (up to three) of females or any number of males can be in the bathroom simultaneously, but never a mix of them. Unlike the readers-writers probelm, if a category of workers is waiting for the shared resource, this does not limit the access to the resource by the other category. For example, if the bathroom is currently occupied by males, other males can come in, but a female arriving at the queue does not prevent more males from going in if there’s enough space. Obviously this leads to starvation.
This problem cannot be solved by simply repurposing sync.RWMutex
.
Let’s sale that “readers” represent females and “writers” represent
males. Any number of females can use the bathroom at the same time, but
only one male would be able to do so. The problem can be solved by
using a sync.Cond
, but the solution is a little convoluted.
A simple solution can be developed using channels, with a pattern that
has been used before in this series. Start by declaring a Bathroom
as
a structure holding some channels:
type Bathroom struct {
m chan Person
f chan Person
exit chan Person
males int
females int
}
The m
and f
channels are used by males and females to queue at the
bathroom. The males
and famales
fields are counters to keep track of
occupancy. The exit
channel is used by a Person
to signal that it’s
leaving the bathroom. To use the bathroom, a Person
does this:
func (p Person) UseBathroom(in, out chan Person) {
in <- p
time.Sleep(time.Duration(250+rand.Intn(100)) * time.Millisecond)
out <- p
}
the person queues at the bathroom door, stays a little in there and then
leaves the bathroom. The Bathroom
itself enforces the constraints:
func (b *Bathroom) Service() {
m := b.m
f := b.f
for {
select {
case <-m:
b.males++
f = nil
if b.males == 3 {
m = nil
}
case <-f:
b.females++
m = nil
if b.females == 3 {
f = nil
}
case p := <-b.exit:
switch p.Gender {
case Male:
b.males--
m = b.m
if b.males == 0 {
f = b.f
}
case Female:
b.females--
f = b.f
if b.females == 0 {
m = b.m
}
}
}
}
}
The two local variables m
and f
are used to control who can enter.
When a male enters the bathroom, f
is set to nil. When a female
enters, m
is set to nil. This based on Dave Cheney’s Channel
Axioms, specifically
“a receive from a nil channel blocks forever”. In this way, when a male
is in the bathroom, other males can enter (because that channel is not
nil) but females cannot. If a male enters the bathroom and the occupancy
reaches 3 people, the m
channel is set to nil to prevent more males
from entering. The same logic applies for females.
When a male leaves the bathroom by sending the signal on the exit
channel, the occupancy is decreased and the channel for entering the
bathroom is restored. If the bathroom becomes empty, then the other
channel is “activated” again. In this way the constraints are satisfied.
Note that while people exiting the bathroom require only one channel,
people entering the bathroom require two because it’s necessary to stop
a category from entering the bathroom when the other is already present.
When exiting, the category doesn’t matter. Also note that the males
and females
counters are not necessary, it’s enough to have an
occupancy
counter, but I kept the two counters for clarity in the
presentation.
I find this solution simpler than the equivalent one based on semaphores if for no other reason that it’s simple to follow.
The full source (with minor modifications for presentation) is here.
The next problem in the book is called the “baboon crossing problem” and it’s conceptually identical to this one. After that comes the “Modus Hall problem”, which is very similar, but contains an interesting variation to the constraints.