It doesn't even require "selecting indefinitely." Every time you create a goroutine you need to worry about how it terminates, or you get a memory leak. If it uses channels, you need to make
damn sure you perform all the proper operations on it, consume all the values from it eventually, etc. It's like manual memory management, except that the invariants are all implicit and poorly specified, like "Take at least 10 values from this channel, and I will terminate" or "Send me a value on this other channel over here and I will terminate". Sounds like a nightmare to reason about in some cases, which is a shame because Go does so much else Just Right^TM
A concrete example, slide 34 [1] contains this code:
func main() {
c := boring("Joe")
for {
select {
case s := <-c:
fmt.Println(s)
case <-time.After(1 * time.Second):
fmt.Println("You're too slow.")
return
}
}
}
This is code that looks really simple, but it's only correct because the process is known to terminate. You couldn't, for example, have the body of this function as a subroutine somewhere: every time you execute it a boring() goroutine is going to be left hanging, which is a classic memory leak. What's worse, "c" looks like an average variable I don't need to worry about (yay garbage collection!) but it's not because it escapes to a running goroutine.
Correct me if I'm wrong but even if boring() was a much simpler function that returned after generating one value, if the timeout happened first it would be left hanging. In that case the only thing that would make the program correct is that of two racing goroutines, one is expected to return first. That's the kind of thing that keeps me up at night, especially when Go makes it so easy to treat goroutines and channels like every other GC-managed data structure when in fact they are every bit as leaky and difficult as threads.
[1]: http://talks.golang.org/2012/concurrency.slide#34