Closing Channels and range

close marks a channel finished so a receiver can tell "no value yet" apart from "no more values ever".

Syntaxclose(ch) // then: v, ok := <-ch or for v := range ch

A receiver often needs to know that nothing more is coming. close(ch) records exactly that. It does not destroy the channel and it does not discard values already sent.

After a close, receives keep working. Values still sitting in the channel come out as normal. Once it is empty, every further receive returns immediately with the element type's zero value: 0 for a number, "" for a string, nil for a pointer.

Telling a real value from a closed channel

The two-result form of a receive reports which happened. v, ok := <-ch sets ok to true when a send delivered v, and false when v is a zero value produced because the channel is closed and empty.

for v := range ch is the shorthand for that check. It receives values until the channel is closed and drained, then the loop ends by itself. It is the same as a bare for loop doing a two-result receive and breaking when ok is false.

Who closes, and what panics

The sender closes. A receiver cannot know whether more sends are coming, and the language will not let it try: close on a receive-only channel does not compile.

Three things panic at runtime. Sending on a closed channel panics with send on closed channel. Closing an already closed channel panics with close of closed channel. Closing a nil channel panics with close of nil channel.

With several senders, none of them may close. Have one goroutine wait for all of them to finish, then close.

Example

Example · go
package main

import "fmt"

// The sender closes, because only the sender knows nothing more is coming.
func send(lines chan<- string) {
	lines <- "first"
	lines <- "second"
	lines <- "third"
	close(lines)
}

func main() {
	lines := make(chan string)
	go send(lines)

	// range over a channel keeps receiving until the channel is closed
	// and drained, then the loop ends on its own.
	for line := range lines {
		fmt.Println("line:", line)
	}

	// A receive from a closed, empty channel returns at once with the
	// zero value. The second result reports whether a send delivered it.
	line, ok := <-lines
	fmt.Printf("after close: %q ok=%v\n", line, ok)
}

// Output:
// line: first
// line: second
// line: third
// after close: "" ok=false

When to use it

  • End a consumer's range loop once the producer has sent its last database row.
  • Close a quit channel to cancel every worker goroutine at once.
  • Tell a caller a stream ended normally rather than returning a zero value it cannot interpret.
  • Shut down a fan-in collector after the last worker has reported.

More examples

range over a channel, and the same loop written out

The shorthand and the longhand of the same loop, with the same answer. <code>for v := range ch</code> is a two-result receive plus a break on <code>!ok</code>, nothing more. Knowing the long form matters for two reasons. You will meet it in code that needs to do something extra when the channel closes, rather than just leaving the loop. And it is the form you must use inside a <code>select</code>, where <code>range</code> is not available, so a case written <code>case v, ok := &lt;-ch:</code> is doing exactly this check by hand.

Example · go
package main

import "fmt"

func fill() chan int {
	ch := make(chan int, 3)
	ch <- 1
	ch <- 2
	ch <- 3
	close(ch)
	return ch
}

func main() {
	// Shorthand. The loop ends by itself once the channel is closed
	// and everything already sent has been received.
	sum := 0
	for v := range fill() {
		sum += v
	}
	fmt.Println("range sum:", sum)

	// Longhand. This is what the range loop above compiles down to.
	ch := fill()
	sum = 0
	for {
		v, ok := <-ch
		if !ok {
			break
		}
		sum += v
	}
	fmt.Println("manual sum:", sum)
}

// Output:
// range sum: 6
// manual sum: 6

Closing as a broadcast to many goroutines

The reason <code>close</code> is more than an end-of-data marker. A value sent on a channel goes to exactly one receiver, so a send cannot tell three goroutines to stop. A close is visible to every receiver, immediately and permanently, so one <code>close(quit)</code> releases all of them however many there are. This is the standard shutdown signal in long-running services, and the channel type is <code>chan struct{}</code> because no data travels, only the fact of the close. <code>struct{}</code> is a type with no fields, so its values take no space.

Example · go
package main

import (
	"fmt"
	"sync"
)

func main() {
	// quit never carries a value. Closing it is the signal. Every receiver
	// sees a close, which is something a single send could never do.
	quit := make(chan struct{})
	released := make(chan int, 3)

	var wg sync.WaitGroup
	for id := 1; id <= 3; id++ {
		wg.Go(func() {
			<-quit // waits here until quit is closed
			released <- id
		})
	}

	close(quit) // frees all three goroutines at once
	wg.Wait()

	fmt.Println("goroutines released:", len(released))
}

// Output:
// goroutines released: 3

Several senders, one owner of the close

The rule "the sender closes" needs amending once there is more than one sender: the last sender closes, and no individual sender knows that it is the last. The answer is a <code>sync.WaitGroup</code> and a goroutine whose only job is to wait and then close. Get this wrong and you have a crash that only appears under load, because it needs two goroutines to reach the same instant. The panic text is <code>panic: send on closed channel</code>, and the stack trace points at the send, not at the close that caused it.

Example · go
package main

import (
	"fmt"
	"sync"
)

func main() {
	out := make(chan int, 4)

	// Two senders. Neither of them may close out: the first close would
	// make the other's next send panic with "send on closed channel".
	var wg sync.WaitGroup
	wg.Go(func() { out <- 1 })
	wg.Go(func() { out <- 2 })

	// A separate goroutine owns the close. It runs only after every sender
	// has finished, so no send can ever happen after the close.
	go func() {
		wg.Wait()
		close(out)
	}()

	total := 0
	for v := range out {
		total += v
	}
	fmt.Println("total:", total)
}

// Output:
// total: 3

Discussion

  • Be the first to comment on this lesson.