The next problem in “The Little Book of Semaphores” is called “the senate bus problem” and reads as follows:
Riders come to a bus stop and wait for a bus. When the bus arrives, all the waiting riders invoke boardBus, but anyone who arrives while the bus is boarding has to wait for the next bus. The capacity of the bus is 50 people; if there are more than 50 people waiting, some will have to wait for the next bus.
When all the waiting riders have boarded, the bus can invoke depart. If the bus arrives when there are no riders, it should depart immediately.
Puzzle: Write synchronization code that enforces all of these constraints.
The trick in this problem is preventing passengers from trying to board the bus after the bus has arrived. It’s very easy to handle if you build a queue of passengers inside a “bus station”. This bus station would accept passengers and start boarding as soon as the bus arrives. Handling the special constraints in this problem becomes trivial:
func (s *BusStation) Run() {
var riders []*Passenger
for {
select {
case p := <-s.gate:
riders = append(riders, p)
case bus := <-s.bus:
// Bus arrived
n := min(bus.capacity, len(riders))
bus.Board(riders[:n])
riders = riders[n:]
}
}
}
A channel is used to get passengers and collect them in a slice. Another channel is used to “receive” the bus when it arrives. When the bus arrives, the passengers start boarding, taking into account the capacity of the bus and the number of passengers in the boarding station. While this is happening, no passengers are getting thru the gate, satisfying the “no boarding after bus arrived” condition.
The Board
method of the bus is trivial:
func (b *Bus) Board(passengers []*Passenger) {
for _, p := range passengers {
p.SignalBoard()
}
}
It simply signals each of the passengers that it has boarded.
SignalBoard
is also trivial:
func (p *Passenger) SignalBoard() {
close(p.sem)
}
A passenger is running this code:
p := NewPassenger(fmt.Sprintf("Passenger %d", i))
station.Enter(p)
p.WaitBoard()
Basically it enters the station and waits until it’s signaled that it
has boarded. WaitBoard
looks like this:
func (p *Passenger) WaitBoard() {
<-p.sem
}
WaitBoard
will block until something returns from the channel, or when
the channel is closed, which is what SignalBoard
does.
station.Enter
looks like this:
func (s *BusStation) Enter(p *Passenger) {
s.gate <- p
}
It simply sends a pointer to the Passenger over the gate
channel,
which the bus station receives and adds to the riders
slice.
This is simpler than the solution presented in the book: the only
explicit signaling that happens is in the WaitBoard
/SignalBoard
methods, and this is happening in the “correct” direction, where the bus
is telling the passenger that it has boarded.
The full source (with minor modifications for presentation) is here.
Next week we go to court.