liveBook · Manning

Closed channel

As explained in Learn Concurrent Programming with Go

A closed channel is a Go channel marked as unable to accept further sends while still allowing pending or zero-valued receives.

A closed channel tells receivers that no more messages will arrive. Calling close(channel) prevents further sends; receiving afterward returns the data type’s default value, such as 0 for int. section 7.1.4

Handle closure explicitly rather than mistaking a valid default value for an end-of-stream signal:

  • Read the value and open-channel flag: msg, more := <-messages. The flag becomes false when the channel is closed and has no more messages. section 7.1.4
  • Range over the channel with for msg := range messages; iteration stops automatically when the channel closes. section 7.1.4
  • In select, remove a closed input by assigning its channel variable to nil; otherwise its case remains continuously ready and can cause needless looping. section 8.1.6

Receiving until closure

A sender emits 1, 2, and 3, then closes the channel. A receiver using for msg := range messages prints those values and finishes, unlike an infinite receive loop that keeps printing 0. section 7.1.4

Listing 8.12 A main() goroutine using the nil select pattern
func main() {
    sales := generateAmounts(50)                #1
    expenses := generateAmounts(40)             #2
    endOfDayAmount := 0
    for sales != nil || expenses != nil {       #3
        select {
        case sale, moreData := <-sales:         #4
            if moreData {
                fmt.Println("Sale of:", sale)
                endOfDayAmount += sale          #5
            } else {
                sales = nil                     #6
            }
        case expense, moreData := <-expenses:   #7
            if moreData {
                fmt.Println("Expense of:", expense)
                endOfDayAmount -= expense       #8
            } else {
                expenses = nil                  #9
            }
        }
    }
    fmt.Println("End of day profit and loss:", endOfDayAmount)
}
#1 Generates 50 amounts on the sales channel
#2 Generates 40 amounts on the expenses channel
#3 Continues to loop while there is a non-nil channel
#4 Consumes the next amount and channel open flag from the sales channel\
#5 Adds the sales amount to the total end-of-day balance
#6 If the channel has been closed, marks the channel as nil, disabling this select case
#7 Consumes the next amount and channel open flag from the expenses channel
#8 Subtracts the expense amount from the total end-of-day balance
#9 If the channel has been closed, marks the channel as nil, disabling this select case

Follow the sales-and-expenses example to see two generated channels consumed with select, disabled individually after closure, and combined into an end-of-day total. section 8.1.6

How Learn Concurrent Programming with Go covers it

How deep it goes
Explains closure behavior, receive flags, range termination, and nil-channel select disabling, while stopping short of deeper runtime internals.
Examples
Uses a three-message receiver, a range loop, and a sales-and-expenses accounting application with independently closing channels.
What it assumes
Assumes Go channels, goroutines, and message passing, while demonstrating closure from basic sends through multiple-channel selection.
What you can do after
Apply it via Go channel receive flags, range loops, and nil-channel select cases to terminate receivers and combine closing inputs.
How it connects
Connects channel closure with sentinel values, message passing, goroutines, select statements, and nil channels.

Also explained in

Compare how 2 books explain closed channel →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Concurrent Programming with Go. The section links point to the parts of the book it draws on.