Vai al contenuto
eLearner.app
Modulo 7 · Lezione 3 di 533/50 nel corso~12 min
Lezioni del modulo (3/5)

`select`: multiplexing di channel

select aspetta su più operazioni su channel contemporaneamente. È il switch della concorrenza Go: ti permette di reagire al primo canale pronto fra molti.

Sintassi

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")
}

Semantica:

  • Tutti i case vengono valutati; viene scelto uno solo dei case "pronti".
  • Se più case sono pronti, la scelta è pseudo-casuale (no ordine).
  • Se nessun case è pronto: il select blocca finché uno lo diventa.
  • Se c'è un default: viene eseguito subito quando nessun case è pronto (select non bloccante).

Pattern timeout

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

time.After(d) ritorna un <-chan Time che produce un valore dopo d. È il modo classico per limitare l'attesa.

Select non bloccante con default

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

"Polla" il canale senza bloccare. Utile in code che devono restare reattive senza attendere obbligatoriamente.

Pattern "done channel"

Combina select con un canale di cancellazione:

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) da parte del coordinatore fa scattare il case <-done in tutte le goroutine in attesa: shutdown coordinato.

Loop con select

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

Il pattern "event loop" tipico dei server e dei worker.

Channel nil come case "spento"

Un trucco avanzato: un case <-ch con ch == nil non viene mai selezionato. Permette di "disabilitare" dinamicamente un case:

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
    }
}

Quando in viene chiuso, lo annullo: il select continua a gestire solo gli altri case.

Prova tu

Esercizio#go.m7.l3.e1
Tentativi: 0Caricamento…

Implementa un select con due case che ricevono da ch1 e ch2; stampa quale è arrivato per primo.

Caricamento editor…
Mostra suggerimento

Sintassi `select { case ...: ... }` con un case per channel.

Soluzione disponibile dopo 3 tentativi

Esercizio#go.m7.l3.e2
Tentativi: 0Caricamento…

Aggiungi al select un case di timeout con time.After(time.Second).

Caricamento editor…
Mostra suggerimento

`time.After(d)` è un channel che riceve dopo d.

Soluzione disponibile dopo 3 tentativi

Quiz#go.m7.l3.e3
Pronto

Cosa rende un select non bloccante?

Go
select { case v := <-ch: ... ??? }
Opzioni di risposta

Recap

  • select aspetta il primo case pronto fra più operazioni su channel.
  • Scelta pseudo-casuale fra case multipli pronti.
  • default → select non bloccante (polling).
  • time.After(d) per timeout (attenzione al leak nei loop: usa context).
  • Pattern: done channel, event loop, channel nil per disabilitare un case.
  • select{} vuoto = blocco eterno: quasi sempre un bug.