Why doesn't Go expose goroutine IDs, and how do you "kill" a goroutine?
The Go team left out goroutine IDs on purpose. With IDs, people would build goroutine-local storage, which hides dependencies and breaks as soon as work moves to another goroutine, for example a pool. Instead, you pass request-scoped values explicitly through context.Context. The runtime does have an internal goid, visible in stack traces. Hacks that parse runtime.Stack to get it exist but are discouraged.
You cannot kill a goroutine from outside. Forcibly stopping one would leave locks held and invariants broken. Cancellation is cooperative: the goroutine has to check for it.
func worker(ctx context.Context, jobs <-chan Job) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case j, ok := <-jobs:
if !ok {
return nil
}
if err := handle(ctx, j); err != nil {
return err
}
}
}
}
For long CPU loops, check ctx.Err() every N iterations. For blocking I/O, set deadlines or close the connection or file, which unblocks the pending call. runtime.Goexit ends only the calling goroutine, after running its deferred calls.
More on Goroutines & the Scheduler
- Q163How do you detect and debug goroutine leaks in production and tests?
- Q164How many goroutines can a Go program run? How do you bound them?
- Q166What happens when main returns while other goroutines are still running? What does this print?
- Q167What happens if a goroutine panics? Can the parent recover it?
- Q168What does this print? (Closure capture and the Go 1.22 loop variable change)
- Q169When does the Go runtime report "all goroutines are asleep - deadlock!" and when doesn't it?