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.