Golang Concepts: Nil Channels
A nil channel in Go is a channel that has been declared but not initialized with a make statement, or explicitly set to nil.
In Go, channels are a core feature used for communication between goroutines. Channels are meant to be used for message sharing and hence when I encountered nil channels, I started to wonder why they were needed.
What are nil channels?
A nil
channel in Go is a channel that has been declared but not initialized with a make statement, or explicitly set to nil
.
A nil channel has the following properties:
Receiving from a nil channel blocks forever.
Sending to a nil channel blocks forever.
Closing a nil channel panics.
Where are nil channels useful?
Let's begin with a fundamental example involving channels, setting the stage for our exploration into the significance of nil
channels:
In the above example, we create two goroutines which send messages on channels a
and b
. Upon completion of sending the values, they also close the channels.
In the main routine, we have an infinite loop consuming the values coming in the channels. We don’t care about the order of processing, so we use a for..select
block for processing the values from the channels.
Following is the output of the above program:
Received value from b: 10
Received value from b: 11
Received value from a: 0
Received value from b: 12
Received value from a: 1
Received value from b: 13
Received value from a: 2
Received value from b: 14
Received value from a: 3
Received value from b: 0
Received value from a: 4
Received value from b: 0
Received value from a: 0
Received value from b: 0
Received value from a: 0
Received value from b: 0
Received value from a: 0
...
We see that an infinite loop keeps running, after printing the values obtained from the channel. The loop keeps on printing 0 values from both channels b & a.
Why?
Once a channel is closed, and all values drained from its buffer, the channel will always return zero values immediately.
So, now let’s understand when the channels are closed, by getting the open value from the channel.
With the above change, while we stopped printing 0 values, we’re still iterating infinitely over a closed channel and printing the same.
Received value from b: 10
Received value from b: 11
Received value from a: 0
Received value from b: 12
Received value from a: 1
Received value from b: 13
Received value from a: 2
Received value from b: 14
Received value from a: 3
Received value from a: 4
Channel a is closed
Channel a is closed
Channel b is closed
Channel b is closed
...
Now, let’s try to break the loop whenever we know that the channel is closed. We can do so by leveraging two variables, aClosed
and bClosed.
Yay! We finally were able to reach a place where we’re no longer infinitely running in the for loop. However, if you notice, we still print Channel a is closed
multiple times. This leads to wasting a few CPU cycles for nothing!
Received value from b: 10
Received value from b: 11
Received value from a: 0
Received value from a: 1
Received value from a: 2
Received value from a: 3
Received value from a: 4
Received value from b: 12
Channel a is closed
Received value from b: 13
Channel a is closed
Channel a is closed
Received value from b: 14
Channel b is closed
Exiting.
Why?
When using a
select
statement with multiple channel operations, if one of the channels is closed, theselect
will immediately resolve to the channel because it can receive immediately (receiving the zero value). Thus, we see theChannel a is closed
message printed multiple times.
Now, let’s try to set the channels to nil.
In a select
statement, which is used for handling multiple channel operations, a nil
channel is effectively ignored(since receiving from nil
channel blocks), and hence we shouldn’t face issues of CPU cycles being wasted over closed channels.
And Voila! We have the perfect state where we’re not wasting any CPU cycles!
Received value from b: 10
Received value from a: 0
Received value from b: 11
Received value from b: 12
Received value from a: 1
Received value from b: 13
Received value from b: 14
Channel b is closed
Received value from a: 2
Received value from a: 3
Received value from a: 4
Channel a is closed
Exiting.