As a silly basic threading exercise, I've been trying to implement the sleeping barber problem in golang. With channels this should be quite easy, but I've run into a heisenbug. That is, when I try to diagnose it, the problem disappears!
Consider the following. The main()
function pushes integers (or "customers") onto the shop
channel. barber()
reads the shop
channel to cut "customers'" hair. If I insert a fmt.Print
statement into the customer()
function, the program runs as expected. Otherwise, barber()
never cuts anyone's hair.
package main
import "fmt"
func customer(id int, shop chan<- int) {
// Enter shop if seats available, otherwise leave
// fmt.Println("Uncomment this line and the program works")
if len(shop) < cap(shop) {
shop <- id
}
}
func barber(shop <-chan int) {
// Cut hair of anyone who enters the shop
for {
fmt.Println("Barber cuts hair of customer", <-shop)
}
}
func main() {
shop := make(chan int, 5) // five seats available
go barber(shop)
for i := 0; ; i++ {
customer(i, shop)
}
}
Any idea what's afoot?
The problem is the way Go's scheduler is implemented. The current goroutine can yield to other goroutines only when it makes a system call or a blocking channel operation. fmt.Println
makes a system call, giving the goroutine an opportunity to yield. Otherwise it doesn't have one.
In practice this doesn't often matter, but for small problems like this it can sometimes crop up.
Also, a more idiomatic, less racy way of doing a non-blocking send on a channel is:
func customer(id int, shop chan<- int) {
// Enter shop if seats available, otherwise leave
select {
case shop <- id:
default:
}
}
The way you're doing it, a customer could end up waiting outside of the barber shop since by the time you actually do the send, len(shop)
may have changed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With