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:

perf_screenshot.png

The Dreaded Default Case

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()
}