Concurrency · 3 MIN READ

select waits on several things; context says stop

Make a blocking operation cancellable without a flag or a kill switch.

select blocks until one of its cases can proceed, and picks randomly among those that are ready. Adding a case for <-ctx.Done() turns any wait into a cancellable one.

The convention that makes this composable: context is the first parameter, it is passed rather than stored, and cancel is always deferred so the resources behind it are released on every path.

SEE THE IDEA

Wait for work and cancellation together

  1. 01

    Work becomes ready

    go
    case v := <-values:

    Continue with a received value

  2. 02

    Cancellation

    go
    case <-ctx.Done():

    Return the context error

  3. 03

    Neither ready

    go
    select { ... }

    The goroutine waits

If several cases can proceed, select chooses one; source order is not a priority rule.

In practice

Make every potentially blocking send or receive part of the cancellation design. A context is a signal, so the operation must actually observe it. When a channel may close, use the receive boolean to distinguish the end of the stream from a real zero-valued item.

Watch out

Cancellation does not forcibly stop a goroutine. Work that never checks ctx.Done can keep running.

Official reference ↗

Work through the exercise

go
package main

import (
	"context"
	"errors"
)

// waitFor should return ctx.Err() when the context is cancelled
// before a value arrives.
func waitFor(ctx context.Context, values <-chan int) (int, error) {
	return <-values, nil
}

func main() { _ = errors.New }
Try it yourself

Trace each value and write down your reasoning. Copy the source into GopherForge or your local Go environment to compile and check it. This web page does not execute your code or send it to a compiler service.

Progress stays in this browser. No account needed.