Go supporta i thread per la gestione di processi asincroni e lo fa in un modo estremamente semplice. Go usa la parola chiave go per avviare una function in modalità asincrona.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package main import ( "fmt" "time" ) func hello() { fmt.Println("ciao mondo") } func main() { hello() go hello() go func() { hello() }() time.Sleep(time.Second * 3) } |
Nell’esempio ho definito la func hello che si limita a stampare a video il classico messaggio “ciao mondo” e per invocarne l’esecuzione chiamo la function anteponendo la parola chiave go, con cui avvio un thread dedicato per l’esecuzione. Notate che ho importato il package time con cui ho impostato un attesa di 5 secondi per vedere il messaggio stampato altrimenti il programma avrebbe finito la sua esecuzione e non avremmo visto niente a schermo. Con semplicità ho gestito in maniera asincrona una funzione che non lo era. La parola chiave go può essere applicata anche a funzioni anonime ottenendo lo stesso effetto.
Le goroutines comunicano tra di loro tramite i channel,canali di comunicazioni monodirezionali detti in informatica pipe. Per creare una pipe si usa la parola chiave make seguito dalla parola chiave chan e il tipo di dato gestito. L’invio o la ricezione del dato avviene tramite la parola chiave -> e <-
1 2 3 4 5 6 7 |
messaggi := make(chan string) go func() { time.Sleep(time.Second * 3) messaggi <- "ciao" }() msg := <-messaggi fmt.Println(msg) |
Nell’esempio ho creato un chanel che accetta stringhe e nella goroutine anonima ho inviato la stringa “ciao” tramite il comando channel <- con un ritardo di 3 secondi. Con il comando <- channel effettuo la lettura del dato e in questo caso non è stato necessario gestire ritardi e sincronizzazione perchè l’esecuzione del codice si interrompe in attesa della lettura del flusso dal channel.
Go mette a disposizione i channel con buffer, ovvero channel che possono ricevere più di un valore in attesa che poi venga letti. La sintassi prevede la dimensione del buffer successivamente al tipo gestito.
1 2 3 |
messaggiBufferend := make(chan string, 2) messaggiBufferend <- "Ciao" messaggiBufferend <- "Mondo" |
Nell’esempio ho creato un channel con dimensione del buffer pari a 2 e su questo ho inviato 2 messaggi separati.
Grazie ai channel è possibile sincronizzare i processi visto che i channel bloccano l’esecuzione dei comandi stando in attesa di un valore.
1 2 3 4 5 6 7 8 9 10 |
func worker(channel chan string) { time.Sleep(time.Second * 3) channel <- "fine" } main () { canale := make(chan string) go worker(canale) msg2 := <-canale fmt.Println(msg2) } |
Nell’esempio ho creato una funzione worker che attende 3 secondi prima di comunicare la fine sul channel ricevuto come parametro. Nel main avvia la goroutine con il worker e resto in attesa dell’esito della elaborazione per chiudere l’esecuzione.
E’ possibile stabilire in sede di passaggio di una channel ad una funzione la direzione del channel, ovvero specificare se è abilitata l’invio o la ricezione
1 2 3 4 5 6 7 8 9 10 11 12 |
func chiamante(channel chan<- string) { channel <- "sono il ricevente" } func ricevente(channel <-chan string) { fmt.Println(<-channel) } main () { can := make(chan string) go chiamante(can) go ricevente(can) } |
Nell’esempio ho definito una funzione chiamante che accetta un channel su cui inviare solo dati, una finzione ricevente che accetta un channel che riceve solo dati e nel main ho lanciato le go routine associate.
Nel prossimo articolo vedremo altre funzioni collegate ai channel