Stopping goroutines with context cancellation

Read the explanation
What if

Three workers share one context. A single cancel() wakes every select watching ctx.Done(), and all three stop.

Predict first

Three workers share one ctx, and main calls cancel() once. How many workers stop?

0123456main: start 3 workersgo ×3time.Sleepcancel()main: <-stopped ×3, printprintselect: work or ctx.Done()worker 1: ctx.Done(): report, returnstopselect: work or ctx.Done()worker 2: ctx.Done(): report, returnstopselect: work or ctx.Done()worker 3: ctx.Done(): report, returnstop

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
Step 1/4

main starts three workers, all sharing one ctx.

1func worker(ctx context.Context, id int, stopped chan<- int) {2    for {3        select {4        case <-ctx.Done(): // cancelled: clean up and return5            stopped <- id6            return7        case <-time.After(10 * time.Millisecond): // one unit of work8        }9    }10}11 12func main() {13    ctx, cancel := context.WithCancel(context.Background())14    stopped := make(chan int)15    for id := 1; id <= 3; id++ {16        go worker(ctx, id, stopped)17    }18    time.Sleep(50 * time.Millisecond) // let them work a while19 20    cancel() // one call reaches all three21    for range 3 {22        fmt.Println("worker", <-stopped, "stopped")23    }24}

Explanation

What just happened

All three workers got the same ctx. Between units of work, each one waited in a select for whichever came first: more work, or ctx.Done(). When main called cancel(), ctx.Done() was closed, and a closed channel wakes every goroutine waiting on it. All three reported and returned.

Try the what-if switch

Why it works like this

Go has no way to kill a goroutine. Stopping is cooperative: the goroutine has to notice and return. A context is the standard way to pass that “please stop” down a call tree, and it also carries deadlines (WithTimeout).

Watch out

Check ctx.Done() often enough. A worker stuck in one long step without checking won’t notice the cancel until that step ends.

Common questions

How do I stop a goroutine in Go?

You can't stop it from outside. Give it a context.Context and have it return when ctx.Done() is closed, checked in a select between units of work.

Does cancelling a parent context cancel its children?

Yes. Every context made from a cancelled parent, with WithCancel, WithTimeout and the rest, is cancelled too, and its Err() is context canceled.

Why didn't my goroutine stop after cancel()?

Because its code never looks at ctx.Done(). cancel() only closes a channel; a goroutine that doesn't watch it keeps running.

Verified: this timeline matches a real program, checked in CI on Go 1.26 and 1.27. How we verify. Unclear or wrong? Tell us