liveBook · Manning

Select statement

As explained in 100 Go Mistakes and How to Avoid Them

A select statement is a Go control-flow construct that waits for and executes a communication operation from among channel cases.

Putting a channel case first does not give it priority. A Go select waits for a communication that can proceed; if several cases are ready, it chooses one by uniform pseudo-random selection, not source order. This avoids favoring a continuously ready channel, but makes case-order assumptions a source of subtle bugs. section 9.4

for {
    select {                         #1
    case v := <-messageCh:           #2
        fmt.Println(v)
    case <-disconnectCh:             #3
        fmt.Println("disconnection, return")
        return
    }
}

Here, placing messageCh before disconnectCh does not ensure that all buffered messages are processed. A producer can send ten messages and then signal disconnection, yet the loop may print only five before returning. section 9.4

for {
    select {
    case v := <-messageCh:
        fmt.Println(v)
    case <-disconnectCh:
        for {                          #1
            select {
            case v := <-messageCh:     #2
                fmt.Println(v)
            default:                   #3
                fmt.Println("disconnection, return")
                return
            }
        }
    }
}

If disconnection means “process messages already available, then return,” enter a second select loop after receiving the notification. Its default case runs when no message receive can proceed. This drains currently available messages; it does not wait for messages that might arrive later. section 9.4

A for/select loop also needs a plan for closed channels. Receiving from a closed channel remains ready and returns its element type’s zero value. In a two-channel merge, ignoring closure can therefore publish zeros repeatedly instead of finishing. Use the two-result receive, `v, open := <-ch`, to distinguish a value from closure. section 9.6

func merge(ch1, ch2 <-chan int) <-chan int {
    ch := make(chan int, 1)
 
    go func() {
        for ch1 != nil || ch2 != nil {    #1
            select {
            case v, open := <-ch1:
                if !open {
                    ch1 = nil             #2
                    break
                }
                ch <- v
            case v, open := <-ch2:
                if !open {
                    ch2 = nil             #3
                    break
                }
                ch <- v
            }
        }
        close(ch)
    }()
 
    return ch
}

After a receive reports `open == false`, setting that input channel to nil removes its receive case from consideration. The loop can keep forwarding from the other input and close the output once both inputs are nil. section 9.6

func f(ctx context.Context) error {
    // ...
    select {              #1
    case <-ctx.Done():
        return ctx.Err()
    case ch1 <- struct{}{}:
    }
 
    select {              #2
    case <-ctx.Done():
        return ctx.Err()
    case v := <-ch2:
        // ...
    }
}

When a function accepts a context, put potentially blocking sends and receives in separate selects alongside `ctx.Done()`. Cancellation can then provide a way out instead of leaving the function blocked on a channel operation. section 8.6.4

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Works through ready-case selection and corrective receive loops, including default and nil-channel cases; does not examine runtime implementation.
Examples
Traces a message-and-disconnection loop that returns early, a two-channel merge that emits zeros, and a context-aware send and receive.
What it assumes
Assumes familiarity with Go goroutines and channel sends and receives, then explains select’s surprising behavior through code.
What you can do after
Diagnose/debug with it when a channel loop exits unpredictably, repeats zero values, or blocks despite cancellation.
How it connects
Connects select to buffered channels, closed channels, nil channels, goroutines, and context cancellation.

Also explained in

Compare how 9 books explain select statement →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.