Search code examples
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:

  • If the channel is still open and has data:
    • msg will contain a valid value from the channel.
    • open will be true.
  • If the channel is closed and still has buffered data:
    • msg will contain the next available buffered values.
    • open will be true.
  • If the channel is closed and empty:
    • msg will contain the zero value of the channel's data type (e.g., "" for strings, 0 for ints).
    • open will be false, indicating that no more data will come, and the channel is fully drained.

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