November 29, 2016
Here’s a bug:
func main() {
channel := make(chan []byte, 100)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
for {
select {
case msg, ok := <-channel:
if ok {
fmt.Println(msg)
} else {
for msg := range channel {
fmt.Println(msg)
}
return
}
default:
/* no-op */
}
}
}()
go func() {
for {
channel <- []byte("Hello world")
time.Sleep(3 * time.Second)
}
}()
wg.Wait()
}
Can you spot it? Here’s a hint:
When you provide a deafult case to a select
statement in Golang, you’re giving the program a route to take when the other cases are blocked.
What we want to happen here is for our program to block on the receive from channel
. While we’re blocked, we don’t need to do anything, but we want to immediately print something as soon as we receive it from the channel.
Unfortunately, by providing a default case, which does nothing, we basically turn this blocking select statement into a spin lock, pinning the CPU while attempting a receive from the channel over and over and over again. Bad news.
Here’s a rewritten version that does what we want:
func main() {
channel := make(chan []byte, 100)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
for {
msg, ok := <-channel
if ok {
fmt.Println(msg)
} else {
for msg := range channel {
fmt.Println(msg)
}
return
}
}
}()
go func() {
for {
channel <- []byte("Hello world")
time.Sleep(3 * time.Second)
}
}()
wg.Wait()
}