Lesson 2 / 25
Goroutines and Their Lifecycle
Starting is easy; knowing who stops a goroutine is the real design question.
Every goroutine needs an exit
The go keyword starts a function call in a new goroutine and returns immediately; there is no handle to join or kill it. A goroutine ends only when its function returns (or the whole program exits: when main returns, all other goroutines are abandoned mid-flight). Goroutines start with small, growable stacks, so creating thousands is normal, but each one still holds memory and anything it references until it returns. Therefore, whenever you write go, answer two questions: how will this goroutine stop, and who waits for it to finish? Typical answers are a closed channel, a cancelled context.Context, and a sync.WaitGroup or errgroup.Group that the caller waits on.
A goroutine with an explicit stop signal
The caller owns both the stop channel and the wait.
package main
import (
"fmt"
"sync"
"time"
)
func heartbeat(stop <-chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
t := time.NewTicker(100 * time.Millisecond)
defer t.Stop()
for {
select {
case <-stop:
fmt.Println("heartbeat: stopping")
return
case <-t.C:
fmt.Println("beat")
}
}
}
func main() {
stop := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go heartbeat(stop, &wg)
time.Sleep(350 * time.Millisecond)
close(stop) // broadcast "stop" to the goroutine
wg.Wait() // and wait until it has actually returned
}Never fix ordering with time.Sleep
Sleeping in main "to let goroutines finish" is a race that happens to pass on your machine. Use a WaitGroup, a channel or errgroup to wait for completion explicitly.
Quick check: What happens to running goroutines when main returns?
- The program exits and they are stopped without running further code
- main waits for them automatically
- They keep running in the background as a daemon
- The runtime panics with a deadlock error
Answer
The program exits and they are stopped without running further code — Program exit does not wait for goroutines and does not run their deferred calls; you must wait for them explicitly.