Search code examples
goconcurrencychannel

Why set a channel input parameter to nil?


I need some help making sense of the Go generics proposal examples.

In particular I am having trouble-so far-understanding two bits of code from the examples section of the proposal entitled "Channels":

The first point I don't get is found in the definition of func Merge[T any](c1,c2 <-chan T) <-chan T: the function instantiates a chan T variable that will hold the result and then it spins up a goroutine that will handle the actuall merging.

In particular, there is an infinite loop that runs as long as at least one of the input channels (to be merged) is not nil.

Code:

// Merge merges two channels of some element type into a single channel.
func Merge[T any](c1, c2 <-chan T) <-chan T {
    r := make(chan T)
    go func(c1, c2 <-chan T, r chan<- T) {
        defer close(r)
        for c1 != nil || c2 != nil {
            select {
            case v1, ok := <-c1:
                if ok {
                    r <- v1
                } else {
                    c1 = nil
                }
            case v2, ok := <-c2:
                if ok {
                    r <- v2
                } else {
                    c2 = nil
                }
            }
        }
    }(c1, c2, r)
    return r
}

In the loop, a select statement combs through the input channels for validly received values (the vn,ok:=<-cn expressions) and then comes the weird part:

If the channel sends valid values, merge and be done with it (r<-vn) but if the channel reports that it just supplied the zero value for its element type (ok==false) then the code in the else branch of both case branches does something incomprehensible (to me): it sets the channel variable to nil!

So, why does the merging goroutine is allowed to set its input channels to nil. Isn't this supposed to be the obligation of the goroutine that is responsible for input channels cn? I'm obviously missing something, please enlighten me.


Solution

  • Setting the channel to nil will prevent from that case getting executed from there on. Note: only the function parameter is set to nil, which is a local variable to the function, the channel object is left intact.

    The function should loop until both input channels are closed, which will likely not happen at the same time. Once a channel is closed, receiving from it can proceed immediately. So if a channel is closed, the case receiving from it could be executed always from there on, immediately, unnecessarily using high CPU, and also it could prevent receiving from the other, non-closed channel (if multiple cases are ready, one is chosen pseudo-randomly, see How does select work when multiple channels are involved?).

    On the other hand, if a (closed) channel is set to nil, it will essentially be taken out from select, because receiving from a nil channel blocks forever (so only the other channel will be monitored from there on). For channel axioms, see How does a non initialized channel behave?