Nell’articolo precedente abbiamo illustrato un primo meccanismo per notificare al ricevente la chiusura del channel, ovvero l’invio di una stringa dedicata che comunica la fine del flusso. Go mette a disposizione la funzione close con cui esplicitare che il canale è stato chiuso, la chiusura non è una operazione obbligatoria come nel caso dei file, ma permette di notificare al ricevente in modo esplicito la chiusura dello stream di dati.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func workerCloser(channel chan string) { channel <- "workerCloser" close(channel) } main() { canale4 := make(chan string) go workerCloser(canale4) msg3, ok := <-canale4 fmt.Println(msg3) msg3, ok = <-canale4 if !ok { fmt.Println("Canale chiuso") } } |
Nell’esempio ho realizzato una funzione che scrive un messaggio e successivamente chiude il canale. Per capire se il canale è stato chiuso o meno leggo il secondo parametro ok di tipo boolean che vale false qualora il canale sia stato chiuso esplicitamente.
Insieme al close go mette a disposizione range, che avevamo visto usato per scorrere gli slices e che applicato ai channel permette di leggere tutti i valori nel channel fino alla chiusura.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func contatore(channel chan int, max int) { for i := 0; i < max; i++ { channel <- i } close(channel) } main() { canale5 := make(chan int, 10) go contatore(canale5, 5) for valore := range canale5 { fmt.Println(valore) } } |
Nell’esempio ho creato una funzione contatore che riceve un parametro in ingresso e tramite ciclo for invia i parametri sul channel. Terminato il ciclo chiude il channel con il comando close. Nel main tramite il range leggo i valori dal channel e il ciclo si interrompe da solo. Notate che in questo caso il valore restituito dal range non l’indice ma il valore stesso.
Go permette di gestire la lettura di più channel concorrenti utilizzando la stessa goroutine. Per fare ciò si avvale del comando select che consente la lettura dei valori da channel differenti. Come sintassi ricorda molto lo switch case.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
go func() { canale6 <- "1" canale6 <- "2" }() go func() { canale7 <- "3" time.Sleep(time.Second * 3) canale7 <- "4" }() for i := 0; i < 4; i++ { select { case mess1 := <-canale6: fmt.Println(mess1) case mess2 := <-canale7: fmt.Println(mess2) default: fmt.Println("Niente") } } |
Nell’esempio ho creato 2 funzioni anonime che scrivono i messaggi su 2 channel separati, tramite il comando select inserito all’interno nel ciclo for recupero tutti i valori inviati. Nel caso di messaggi concorrenti la select sceglie casualmente quale processare per primo. Con lo statement default possiamo implementare della logica da eseguire qualora non sia presente alcun dato nei channel gestiti dal select. Senza default la select rimarrà in attesa di messaggi dal channel ad ogni iterazione.
Il costrutto select consente anche di implementare in modo agevole un meccanismo di timeout tramite la funzione After del package time.
1 2 3 4 5 6 7 8 |
for i := 0; i < 2; i++ { select { case mess1 := <-canale6: fmt.Println(mess1) case <-time.After(time.Second * 2): fmt.Println("timeout") } } |
La funzione After simula la ricezione di un messaggio nel channel, in tal modo implementiamo una attesa di 2 secondi sul channel canale, trascorso il quale viene gestita la condizione di timeout.
Infine qualora si voglia gestire l’accesso concorrente a risorse di più goroutines Go mette a disposizioni le funzioni Lock e Unlock del package sync. I 2 comandi ci assicurano che solo un thread per volta potrà eseguire il codice compreso tra i 2 statement
1 2 3 4 5 6 |
func (cont *contatoreStruct) somma(val int) { var mutex sync.Mutex mutex.Lock() cont.counter = cont.counter + val mutex.Unlock() } |