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
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
selectblocca finché uno lo diventa. - Se c'è un
default: viene eseguito subito quando nessun case è pronto (select non bloccante).
Pattern timeout
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
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:
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
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:
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
Implementa un select con due case che ricevono da ch1 e ch2; stampa quale è arrivato per primo.
Mostra suggerimento
Sintassi `select { case ...: ... }` con un case per channel.
Soluzione disponibile dopo 3 tentativi
Aggiungi al select un case di timeout con time.After(time.Second).
Mostra suggerimento
`time.After(d)` è un channel che riceve dopo d.
Soluzione disponibile dopo 3 tentativi
Cosa rende un select non bloccante?
select { case v := <-ch: ... ??? }Recap
selectaspetta 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: usacontext).- Pattern: done channel, event loop, channel nil per disabilitare un case.
select{}vuoto = blocco eterno: quasi sempre un bug.