It is a good idea to allow the loop to end in order to avoid goroutine and resource leakage. Some of these issues are as follows:
- When a goroutine hangs without returning, the space in memory remains used, contributing to the application's size in memory. The goroutine and the variables it defines in the stack will get collected by the GC only when the goroutine returns or panics.
- If a file remains open, this can prevent other processes from executing operations on it. If the number of files that are open reaches the limit imposed by the OS, the process will not be able to open other files (or accept network connections).
An easy solution to this problem is to always use context.Context so that you have a well-defined exit point for the goroutine:
func NewGenInt64(ctx context.Context) genInt64 {
g := genInt64{ch: make(chan int64)}
go func() {
for i := int64(0); ; i++ {
select {
case g.ch <- i:
// do nothing
case <-ctx.Done():
close(g.ch)
return
}
}
}()
return g
}
This can be used to generate values until there is a need for them and cancel the context when there's no need for new values. The same pattern can be applied to a version that returns a channel. For instance, we could use the cancel function directly or set a timeout on the context:
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
defer cancel()
g := NewGenInt64(ctx)
for i := range g.ch {
go func(i int64) {
fmt.Println(i, g.Next())
}(i)
}
time.Sleep(time.Second)
}
The generator will produce numbers until the context that's provided expires. At this point, the generator will close the channel.