The author of “The Little Book of Semaphores” wrote this week’s problem inspired by real life events at Colby College. The problem’s statement is the following:
The following synchronization constraints apply to students and the Dean of Students:
Any number of students can be in a room at the same time.
The Dean of Students can only enter a room if there are no students in the room (to conduct a search) or if there are more than 50 students in the room (to break up the party).
While the Dean of Students is in the room, no additional students may enter, but students may leave.
The Dean of Students may not leave the room until all students have left.
There is only one Dean of Students, so you do not have to enforce exclusion among multiple deans.
Puzzle: write synchronization code for students and for the Dean of Students that enforces all of these constraints.
This problem is very similar to the previous one (the childcare center problem) in the sense that it’s about limiting access to a critical section to a particular thread only if thread of specific types are already present or not present.
A student can be represented by a goroutine like this:
go func() {
s := Student{Name: fmt.Sprintf("Student %d", i)}
r.sIn <- s
// ...
r.sOut <- s
}()
When the goroutine is sending to the sIn
channel, the student is
entering the room. After the send finishes, the student is in the
room. When it’s sending to the sOut
channel, it’s signaling that it
wants to leave the room, and after the send finishes, the student has
left the room.
The controlling logic is actually in the room itself:
func (r *Room) Run(timeout <-chan time.Time) {
var (
n int
dIn = r.dIn
dOut = r.dOut
sIn = r.sIn
)
The variables dIn
, dOut
and sIn
are used to control receiving from
those channels. When there are no students present, dIn
is not nil
to allow the dean to enter the room and conduct a search. During the
search, sIn
is nil
to prevent students from entering. If the dean
leaves, sIn
is not nil
to allow students to enter the room and dIn
is set to nil
to prevent the dean from entering. If enough students
enter the room, dIn
is set to a non-nil
value to allow the dean to
enter to break up the party. While students are leaving, sIn
is nil
and dOut
is also nil
to prevent the dean from leaving too early.
This is expressed in a for-select
loop:
for {
select {
case <-dIn:
// Dean entered, students cannot enter
sIn = nil
case <-dOut:
// Dean left, students can enter
sIn = r.sIn
case s := <-sIn:
n++
case s := <-r.sOut:
n--
case <-timeout:
return
}
In order to make sure that all the conditions are met at all times, the
check is performed after the select
, like this:
switch {
case n == 0:
// Dean can enter or leave
dIn = r.dIn
dOut = r.dOut
case n > r.Cap:
// Dean can enter
dIn = r.dIn
dOut = nil
default:
// Dean cannot enter
dIn = nil
dOut = nil
}
}
}
Conceptually, this solution is the same as the one for the previous problem. It’s interesting to note that this is not true for the semaphore-based solution in the book. The key thing to note in this and prior solutions is that I have introduced “additional” threads to represent the contended resource in one way or another. For example, in this problem the room is a goroutine that does the bookkeeping. It’s this “extra” goroutine that simplifies the solution. Instead of having the dean and the students communicate by sharing memory, they share memory by communicating.
The full source (with minor modifications for presentation) is here.
The next problem in the book deals with boarding a bus.