Select
Il costrutto select
serve ad evitare il blocco quando un channel in lettura è vuoto.
Il costrutto select
è come switch
ma concepito apposta per operare su channel.
Il primo dei case
alternativi che si verifica soddisfa il select
.
Esempio
(270select.go):
package main
import "time"
import "fmt"
func main() {
// Due channel
c1 := make(chan string)
c2 := make(chan string)
// Ogni channel riceve un valore dopo un ritardo
// generato da due goroutines diverse
go func() {
time.Sleep(time.Second * 2)
c1 <- "one"
}()
go func() {
time.Sleep(time.Second * 1)
c2 <- "two"
}()
// select ascolta su entrambi i channel
// Come ricezione eventi
for i := 0; i < 2; i++ {
// select esce col primo case verificato
// Perciò il for
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
Nell'esempio sopra il for
cicla esattamente due volte sul select
, perchè sappiamo che vi sono due messaggi in arrivo. La situazione reale sarebbe più complessa.
Fibonacci con goroutine e channels
Nel caso seguente deleghiamo una goroutine al calcolo dei numeri di Fibonacci.
Oltre al channel ch
che contiene i numeri cacolati si usa il channel done
per indicare alla goroutine la terminazione del lavoro.
(271-fibonacci.go):
package main
import "fmt"
func main() {
// ch : channel per i dati
// quit: channel per indicare terminazione
ch, quit := make(chan int), make(chan int)
// lancio della goroutine
go fibs(ch, quit)
// raccolta di 20 dati
for i := 0; i < 20; i++ {
fmt.Println(<-ch)
}
// segnala terminazione
quit <- 0
}
func fibs(ch, quit chan int) {
i, j := 0, 1
for {
select {
// se c'è qualcuno che riceve
// invia il prossimo valore
case ch <- j:
i, j = j, i+j
// se si riceve terminazione esce
case <-quit:
return
}
}
}
La goroutine fibs
pone il numero corrente su ch
.
Se qualcuno legge il channel ch
allora viene calcolato il prossimo numero di Fibonacci e si torna in ciclo.
Se non vi fosse il ciclo la goroutine terminerebbe dopo il primo numero di Fibonacci e il main si bloccherebbe per sempre tentando di leggere il secondo.
Se invece vi è un valore sul channel quit
, la goroutine termina.
Il main legge 20 volte dal channel ch
poi pone un valore sul channel done
.
In questo caso se non l'avesse fatto il programma sarebbe terminato lo stesso. Ma questa procedura è buona norma perchè il programma potrebbe essere successivamente modificato e ci potremmo trovare con una goroutine spinning all'infinito senza fornire lavoro produttivo.
Il fatto che i parametri formali e attuali - i nomi dei channel - siano gli stessi, non è necessario: la funzione di goroutine poteva benissimo essere in un altro package e usare nomi diversi per i propri parametri formali.