Skip to main content
eLearner.app
Module 7 · Lesson 3 of 533/50 in the course~12 min
Module lessons (3/5)

`select`: multiplexing channels

select waits on multiple channel operations at the same time. It is the switch of Go concurrency: it lets you react to the first channel ready among many.

Syntax

Go
select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
case ch3 <- 99:
    fmt.Println("inviato su ch3")
case <-time.After(time.Second):
    fmt.Println("timeout")
default:
    fmt.Println("nessuno è pronto")
}

Semantics:

  • All cases are evaluated; only one of the ready cases is chosen.
  • If multiple cases are ready, the choice is pseudo-random (no ordering).
  • If no case is ready: the select blocks until one becomes ready.
  • If there is a default: it runs immediately when no case is ready (non-blocking select).

Timeout pattern

Go
select {
case res := <-fetch(url):
    use(res)
case <-time.After(2 * time.Second):
    log.Println("timeout: fetch troppo lento")
}

time.After(d) returns a <-chan Time that produces a value after d. It is the classic way to bound the wait.

Non-blocking select with default

Go
select {
case msg := <-ch:
    process(msg)
default:
    // niente da fare, prosegui
}

It "polls" the channel without blocking. Useful in code paths that must stay responsive without mandatorily waiting.

"Done channel" pattern

Combine select with a cancellation channel:

Go
func worker(in <-chan Job, done <-chan struct{}) {
    for {
        select {
        case j := <-in:
            process(j)
        case <-done:
            return       // chi gestisce done segnala lo shutdown
        }
    }
}

close(done) from the coordinator triggers case <-done in every waiting goroutine: coordinated shutdown.

Loop with select

Go
for {
    select {
    case v := <-input:
        if v == nil {
            return
        }
        handle(v)
    case <-time.After(5 * time.Second):
        keepAlive()
    case <-ctx.Done():
        return
    }
}

The typical "event loop" pattern for servers and workers.

A nil channel as a "disabled" case

An advanced trick: a case <-ch with ch == nil is never selected. It lets you "disable" a case dynamically:

Go
var in chan int = source()
for {
    select {
    case v, ok := <-in:
        if !ok {
            in = nil    // disabilita questo case
            continue
        }
        handle(v)
    case <-ctx.Done():
        return
    }
}

When in is closed, I null it: the select keeps handling only the other cases.

Try it

Exercise#go.m7.l3.e1
Attempts: 0Loading…

Implement a select with two cases that receive from ch1 and ch2; print which one arrived first.

Loading editor…
Show hint

Syntax `select { case ...: ... }` with one case per channel.

Solution available after 3 attempts

Exercise#go.m7.l3.e2
Attempts: 0Loading…

Add a timeout case to the select using time.After(time.Second).

Loading editor…
Show hint

`time.After(d)` is a channel that receives after d.

Solution available after 3 attempts

Quiz#go.m7.l3.e3
Ready

What makes a select non-blocking?

Go
select { case v := <-ch: ... ??? }
Answer options

Recap

  • select waits for the first ready case among several channel operations.
  • Pseudo-random choice among multiple ready cases.
  • default → non-blocking select (polling).
  • time.After(d) for timeouts (mind the leak in loops: use context).
  • Patterns: done channel, event loop, nil channel to disable a case.
  • Empty select{} = eternal block: almost always a bug.