gochannelgoroutinebuffered

Golang, Buffered Channel and its closing


I have a code snippet and here, the message channel is buffered and I close the buffered channel on the run will this code snippet work or do I have to check explicitly whether some data still exists on the message channel?

messages := make(chan string, 9)
for {
    select {
    case msg, open := <-messages:
        if !open {
            fmt.Println("Channel closed, stopping")
            return
        }
        fmt.Println("Received:", msg)
    default:
        fmt.Println("No messages available, continuing...")
    }
}

As per my understanding:

Doubt in this point mainly: If the channel is closed and still has buffered data. Do I have to manually drain the channel to make sure no data is missed, before breaking out from the loop?


Solution

  • Your understanding is correct. This is covered in the channel types section of the spec:

    Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives).

    and

    The multi-valued assignment form of the receive operator reports whether a received value was sent before the channel was closed.

    The close section is also relevant:

    After calling close, and after any previously sent values have been received, receive operations will return the zero value for the channel's type without blocking. The multi-valued receive operation returns a received value along with an indication of whether the channel is closed.

    Do I have to manually drain the channel to make sure no data is missed, before breaking out from the loop?

    This depends on your use-case. If you want to ensure all data passed into the channel is processed then you will need to drain it (which your code will do as-is). If the data can be thrown away then there is no need to drain the channel (but channel closure is often used to signal that the receiver should terminate).

    Note that running a tight loop, as shown in your question, is generally a bad idea (the code will uses a lot of CPU cycles, whilst having the same outcome as receiving on the channel without the select). I'm assuming the example is part of a larger function (which does other things within the loop).

    It's fairly easy to test this (playground):

    func main() {
        messages := make(chan string, 9)
    
        read(messages)
        messages <- "one"
        read(messages)
        read(messages)
        messages <- "two"
        messages <- "three"
        close(messages)
        read(messages)
        read(messages)
        read(messages)
    }
    
    func read(messages <-chan string) {
        select {
        case msg, open := <-messages:
            if !open {
                fmt.Println("Channel closed, stopping")
                return
            }
            fmt.Println("Received:", msg)
        default:
            fmt.Println("No messages available, continuing...")
        }
    }
    

    Output:

    No messages available, continuing...
    Received: one
    No messages available, continuing...
    Received: two
    Received: three
    Channel closed, stopping