I'm working through the Concurrency section of A Tour of Go, and I'm curious about proper Go convention for consuming finite channels. In this exercise, I need to read values from two channels and determine if the values are the same and in the same order. If not, I can immediately return false
from my method. However, if I do that, will Go clean up my channels for me automatically, or will my goroutines be left hanging forever and consuming resources?
The best way to handle this would be to pass a cancel channel into my goroutines, but since the goroutines read a finite amount of data, it seems fine to just consume all the data. What is the best way to handle this case in real life?
Using quit channels, as discussed in the Go Concurrency Patterns: Pipelines and cancellation blog article and Heath Borders' answer, is often a good idea.
There is also the golang.org/x/net/context
package discussed in the Go Concurrency Patterns: Context blog article which adds timeouts, deadlines, and other features.
However, to directly address:
will Go clean up my channels for me automatically
It depends on the channel buffering and how the channels are written to.
E.g.
func writeValues(n int, c chan int) {
for i := 0; i < n; i++ {
c <- i
}
log.Println("writeValues", n, "done")
}
func main() {
ch1 := make(chan int, 12)
ch2 := make(chan int, 6)
ch3 := make(chan int)
go writeValues(10, ch1)
go writeValues(11, ch2)
go writeValues(12, ch3)
time.Sleep(time.Second) // XXX
}
Here the first goroutine will complete and ch1
(and anything buffered in it) would be garbage collected and cleaned up. However, the later two goroutines will block waiting until they can write all their values. The garbage collector would never touch ch2
and ch3
since the blocked goroutines keep a reference to them. Note ch2
would get cleaned up if as few as five items where read from the channel.
Usually, you only rely on this when doing something like:
errc := make(chan error, 1)
go func() { errc <- someErrorReturningFunc() }()
If the function being called has no way to cancel it, then this is a common idiom. You can do this and abort/return early without reading from errc
and know that the goroutine and channel will be cleaned up when the function eventually returns. The buffer size of the errc
channel is important here.
Andrew Gerrand's talk at Gophercon covers this exact question on slide 37.
Create a quit channel and pass it to each walker. By closing quit when the Same exits, any running walkers are terminated.
func Same(t1, t2 *tree.Tree) bool {
quit := make(chan struct{})
defer close(quit)
w1, w2 := Walk(t1, quit), Walk(t2, quit)
for {
v1, ok1 := <-w1
v2, ok2 := <-w2
if v1 != v2 || ok1 != ok2 {
return false
}
if !ok1 {
return true
}
}
}