Select in Go
L’istruzione select in Go permette a una goroutine di attendere su piu operazioni con i canali contemporaneamente. E uno strumento fondamentale per gestire la concorrenza in modo elegante e sicuro.
Sintassi Base
La select funziona in modo simile a uno switch, ma ogni caso riguarda un’operazione su un canale:
select {
case valore := <-ch1:
fmt.Println("Ricevuto da ch1:", valore)
case ch2 <- 42:
fmt.Println("Inviato a ch2")
case valore := <-ch3:
fmt.Println("Ricevuto da ch3:", valore)
}
La select blocca l’esecuzione fino a quando uno dei casi e pronto. Se piu casi sono pronti contemporaneamente, ne viene scelto uno in modo casuale.
Multiplexing dei Canali
Il caso d’uso principale della select e il multiplexing, cioe la gestione simultanea di piu canali:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "risultato da ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "risultato da ch2"
}()
// Riceve dal primo canale che e pronto
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
In questo esempio, la select ricevera il messaggio da ch1 perche e pronto prima di ch2.
Caso Default
Il caso default rende la select non bloccante. Se nessun altro caso e pronto, viene eseguito il default:
select {
case valore := <-ch:
fmt.Println("Ricevuto:", valore)
default:
fmt.Println("Nessun dato disponibile")
}
Questo e utile quando non si vuole bloccare l’esecuzione in attesa di un canale.
Timeout con time.After
Un pattern molto comune e l’uso di time.After per impostare un timeout su un’operazione con canale:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
time.Sleep(3 * time.Second) // simula un'operazione lenta
ch <- "risultato"
}()
select {
case risultato := <-ch:
fmt.Println("Ricevuto:", risultato)
case <-time.After(2 * time.Second):
fmt.Println("Timeout: operazione troppo lenta")
}
}
time.After restituisce un canale che riceve un valore dopo la durata specificata. Se il risultato non arriva in tempo, il caso del timeout viene eseguito.
Operazioni Non Bloccanti
Combinando select con default, si possono creare operazioni non bloccanti di invio e ricezione:
Ricezione non bloccante
ch := make(chan int, 1)
select {
case valore := <-ch:
fmt.Println("Ricevuto:", valore)
default:
fmt.Println("Il canale e vuoto, continuo l'esecuzione")
}
Invio non bloccante
ch := make(chan int, 1)
ch <- 1 // riempe il buffer
select {
case ch <- 2:
fmt.Println("Inviato con successo")
default:
fmt.Println("Il canale e pieno, non posso inviare")
}
Select con Ciclo For
Uno dei pattern piu comuni e combinare select con un ciclo for per gestire continuamente eventi da piu canali:
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(500 * time.Millisecond)
boom := time.After(2 * time.Second)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(100 * time.Millisecond)
}
}
}
Pattern con Canale di Uscita
Un pattern robusto per terminare un ciclo select in modo controllato:
package main
import (
"fmt"
"time"
)
func generatore(esci <-chan struct{}) <-chan int {
ch := make(chan int)
go func() {
defer close(ch)
i := 0
for {
select {
case ch <- i:
i++
case <-esci:
fmt.Println("Generatore terminato")
return
}
}
}()
return ch
}
func main() {
esci := make(chan struct{})
numeri := generatore(esci)
for i := 0; i < 5; i++ {
fmt.Println(<-numeri)
}
close(esci) // segnala al generatore di terminare
time.Sleep(100 * time.Millisecond)
}
Select con Context (Go 1.23+)
In applicazioni reali, si usa spesso il context per gestire la cancellazione in combinazione con select:
package main
import (
"context"
"fmt"
"time"
)
func elabora(ctx context.Context, risultati chan<- string) {
for i := 1; ; i++ {
select {
case <-ctx.Done():
fmt.Println("Elaborazione annullata")
return
case risultati <- fmt.Sprintf("Risultato %d", i):
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
risultati := make(chan string)
go elabora(ctx, risultati)
for {
select {
case r := <-risultati:
fmt.Println(r)
case <-ctx.Done():
fmt.Println("Timeout raggiunto")
return
}
}
}
Conclusione
L’istruzione select e uno strumento potente e idiomatico in Go per la gestione della concorrenza. Permette di multiplexare canali, implementare timeout, eseguire operazioni non bloccanti e gestire la terminazione controllata delle goroutine. Padroneggiare la select e essenziale per scrivere programmi Go concorrenti robusti e ben strutturati.