Creating channels

May 18, 2023

Channel types

A new, initialized channel value can be made using the built-in function make, which takes the channel type and an optional capacity as arguments:

make(chan int, 100)

Here we have the semi-magical make built-in function again. We’ve already seen it for use with arrays and slices. Consider this yet another clue that a channel is just another type, similar to array and slice, in that it “just” holds a bunch of values, and isn’t magical in any way.

The capacity, in number of elements, sets the size of the buffer in the channel. If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives). A nil channel is never ready for communication.

I find this description to unnecessarily call out unbuffered channels as seeming like a special case. They really aren’t.

The rules when it comes to whether or not a send or receive operation blocks is simple:

  • A send to a channel blocks if there is no spare capacity to receive the new element.
  • A receive from a channel blocks if there are no elements on the channel to be received.

This means that:

  • Sending to a cahnnel with capacity 10, and 9 or fewer elements in it, will not block.
  • Sending to a channel with capacity 10, and with 10 elements in it, will block until one of the elements is read.
  • Sending to a channel with capacity 0 will block until that same element is read.
  • Sending to a nil channel will block forever.
  • Receiving from a channel with capacity 10 and 1 or more elements in it will not block.
  • Receiving from a channel with capacity 10 and 0 elements in it will block until an element is sent.
  • Receiving from a channel with capacity 0 will block until an element is sent.
  • Recieving from a nil channel will block forever.

Quotes from The Go Programming Language Specification Version of December 15, 2022

Share this

Related Content

Type identities of other types

Type identity … Two pointer types are identical if they have identical base types. Given: type ( P = *int Q = P R *int ) P, Q, and *int are all identical to each other, but different from *int32 and R. Two function types are identical if they have the same number of parameters and result values, corresponding parameter and result types are identical, and either both functions are variadic or neither is.

Channel synchronization

I’ve made the case that channels are just data types, and non-magical. So why should we care about channels, rather than just using some sort of slice, array, or custom first-in-first-out queue? The reason is that channels provide us certain guarantees with regard to synchronization. The spec explains: Channel types … A single channel may be used in send statements, receive operations, and calls to the built-in functions cap and len by any number of goroutines without further synchronization.

Closing channels

Channel types … A channel may be closed with the built-in function close. The multi-valued assignment form of the receive operator reports whether a received value was sent before the channel was closed. Closing a channel is a simple matter of using the built-in close function: // ch must be of type `chan T` or `chan<- T`. A receive-only channel (`<-chan T`) cannot be closed. close(ch) Calling close on a channel takes immediate effect, and prevents further values from being written to the channel (any items already in the channel may still be read).