Explicitly closing channels


(.) #1

Example below works fine (not sure if it is the best practise though) with/without explicitly closing channels. However, what I want to know is, is there any value/benefit of explicitly closing channels or leaving it to memory manager to handle/close them?

Some resources confusing and make similar remarks such as:

  • Close what you opened after finishing with them.
  • Don’t worry too much about closing channels because Go is good at handling things like that.

I have a strong feeling that, it is better to close them because a book I read reads: “Channels carry overhead and have performance impact… Channnels are single biggest source of memory management issues in Go programs.

Note: I know I could use “timeout” to exit the loop but using done channel on purpose.

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	fmt.Println("main: begin")
    
    	names := []string{"John", "Robert", "Al", "Rick"}
    
    	msg := make(chan string)
    	done := make(chan bool)
    
    	go greet(msg, done, names)
    
    	Loop:
    		for {
    			select {
    			case m := <-msg:
    				fmt.Println(m)
    			case <-done:
    				fmt.Println("main: done")
    				//close(done)
    				break Loop
    			}
    		}
    
    	fmt.Println("main: end")
    }
    
    func greet(msg chan<- string, done chan<- bool, name []string) {
    	for _, name := range name {
    		msg <- fmt.Sprintf("Hi %s!", name)
    	}
    
    	//close(msg)
    
    	done <- true
    }
main: begin
Hi John!
Hi Robert!
Hi Al!
Hi Rick!
main: done
main: end

(Lutz Horn) #2

Note that in your example a single channel msg would be enough. As soon as greet calls close(msg), a for loop readig values m := <-msg would end. The second chanel done and the select are not necessary.

But I guess this is not what you question is about :wink:


(.) #3

Your comment is perfectly fine and much appreciated. Learning in progress …


( Kvaz1r) #4

Maybe you also fine this article useful - How to Gracefully Close Channels


(.) #5

@GreyShatter I read it before and I think too much going on there. I lost my bearings.

@lutzhorn Do you mind showing me how exactly I should refactor it because when I do what you suggest, it either prints once and exit or don’t exit at all. I bet am missing something.


( Kvaz1r) #6

Strange, usually they articles are very good and simple enough.

As for refactoring:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("main: begin")

	names := []string{"John", "Robert", "Al", "Rick"}

	msg := make(chan string)

	go greet(msg, names)

	for m := range msg {
		fmt.Println(m)
	}

	fmt.Println("main: end")
}

func greet(msg chan<- string, names []string) {
	for _, name := range names {
		msg <- fmt.Sprintf("Hi %s!", name)
	}
	close(msg)
}

https://play.golang.org/p/75vASSZdAbf


(.) #7

Excellent. Thanks. I remember seeing range bit before but completely forgot about it. Now it is time for me to wait for comments on “to close or not to close channels” question.


( Kvaz1r) #8

Well it’s totally okay - https://stackoverflow.com/questions/8593645/is-it-ok-to-leave-a-channel-open

But with close GC release memory sooner and your code became more readable so I personally always exlicitly close channels.


(Lutz Horn) #9

Try something like this:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("main: begin")

	names := []string{"John", "Robert", "Al", "Rick"}

	msg := make(chan string)

	go greet(msg, names)

	// A: Using range on the channel will loop until the channel is closed.
	for m := range msg {
		fmt.Println(m)
	}

	fmt.Println("main: end")
}

func greet(msg chan<- string, name []string) {
	for _, name := range name {
		msg <- fmt.Sprintf("Hi %s!", name)
	}

	// Closing msg here will terminate the loop at A.
	close(msg)
}

See https://play.golang.com/p/864dawlHgiD