Skip to content

Latest commit

 

History

History
79 lines (62 loc) · 1.76 KB

goroutine-forget.md

File metadata and controls

79 lines (62 loc) · 1.76 KB

Don't fire-and-forget goroutines

Goroutines are lightweight, but they're not free: at minimum, they cost memory for their stack and CPU to be scheduled. While these costs are small for typical uses of goroutines, they can cause significant performance issues when spawned in large numbers without controlled lifetimes. Goroutines with unmanaged lifetimes can also cause other issues like preventing unused objects from being garbage collected and holding onto resources that are otherwise no longer used.

Therefore, do not leak goroutines in production code. Use go.uber.org/goleak to test for goroutine leaks inside packages that may spawn goroutines.

In general, every goroutine:

  • must have a predictable time at which it will stop running; or
  • there must be a way to signal to the goroutine that it should stop

In both cases, there must be a way code to block and wait for the goroutine to finish.

For example:

BadGood
go func() {
  for {
    flush()
    time.Sleep(delay)
  }
}()
var (
  stop = make(chan struct{}) // tells the goroutine to stop
  done = make(chan struct{}) // tells us that the goroutine exited
)
go func() {
  defer close(done)

  ticker := time.NewTicker(delay)
  defer ticker.Stop()
  for {
    select {
    case <-ticker.C:
      flush()
    case <-stop:
      return
    }
  }
}()

// Elsewhere...
close(stop)  // signal the goroutine to stop
<-done       // and wait for it to exit

There's no way to stop this goroutine. This will run until the application exits.

This goroutine can be stopped with close(stop), and we can wait for it to exit with <-done.