Go - from Google


Goroutines     

Go ha un eccellente supporto per la concorrenza ed il networking. Fin dall'inizio la sua inclinazione è andata in questa direzione ed i risultati, stando a quanto risulta dai feedback provenienti dalla comunità, sembrano essere positivi. I nomi indicati nel titolo sono il nocciolo di tutta la faccenda in questo linguaggio. Non starò a dilungarmi sulla grande importanza che il parallelismo, la concorrenza e similari hanno nell'ambito della programmazione moderna, il web è pieno di discussioni in merito. Ogni linguaggio in pratica ha degli strumenti per la gestione di questi meccanismi e Go, linguaggio moderno per nascita, non fa eccezione garantendo un supporto al alto livello che rende più semplice questo delicato tipo di programmazione.
Iniziamo quindi a parlare delle goroutines che, detto molto semplicemente, sono delle funzioni in grado di girare concorrentemente ad altre funzioni. E' importante notare che la concorrenza di cui parliamo non implica necessariamente il parallelismo; nel caso diciamo "base", non abbiamo un uso di più core simultaneamente, solo uno è fisicamente dedicato al programma, di default e solo una funzione per volta può essere eseguita; tuttavia esiste una concorrenza nella loro esecuzione. Affinchè sia realizzato pieno parallelismo è necessario agire sulla variabile GOMAXPROCS che gestisce il numero di threads a livello di sistema operativo in grado di eseguire codice Go user level. Ma questo è un altro discorso, per il momento ci interessiamo solo alle goroutines. A questo proposito non bisogna confondere queste con i threads: si tratta di cose diverse, è possibile che girino più Goroutines, che sono come detto funzioni, per ogni threads. Le goroutines realizzano una concorrenza in formato leggero in quanto con bassi requisti di risorse. Le Goroutines vengono utili in molti esempli applicativi complessi, in questo paragrafo ci limiteremo ad esempi didattici, seguendo le nostre impostazioni generali. In altri linguaggi esiste il concetto di coroutines, molto simili, ma esistono due differenza sostanziali: le Goroutines possono essere eseguite in parallelo, le coroutines di norma no, inoltre le prime comunicano via channels, meccanismo altrove assente e che approfondiremo nel prossimo paragrafo.

Formalmente la cosa è molto semplice, le goroutines vengono introdotte dalla keyword go seguita dall'invocazione di una funzione.

go nomefunzione(eventuali parametri)

Un primo esempio di funzionamento lo vediamo qui di seguito:

  Esempio 15.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"time"
)

func main() {
fmt.Println("Inizio")
go AttesaLunga()
go AttesaBreve()
go AttesaMedia()
time.Sleep(10 * 1e9)
fmt.Println("Fine")
}

func AttesaLunga() {
fmt.Println("Inizio attesa lunga")
time.Sleep(5 * 1e9)
fmt.Println("Fine attesa lunga")
}

func AttesaBreve() {
fmt.Println("Inizio attesa breve")
time.Sleep(2 * 1e9)
fmt.Println("Fine attesa breve")
}

func AttesaMedia() {
fmt.Println("Inizio attesa media")
time.Sleep(3 * 1e9)
fmt.Println("Fine attesa media")
}

Questo esempio, adattato da quello presente nel testo di Balbaert, è significativo se lo eseguite e, successivamente provate a rifarlo dopo aver eliminato le 3 paroline magiche "go" presenti alle righe 10, 11 e 12. Si vedrà chiaramente come, nel primo caso, lo stop delle funzioni imposto dall'istruzione sleep permette la partenza delle altre chiamate successive (l'ordine non è garantito, questo è importante) mentre nel caso "normale", senza l'istruzione "go", la sequenza di esecuzione e l'ordine, sono stringenti e sempre eguali. Da notare che anche la funzionae main è messa in pausa in quanto, diversamente,  3 thread non farebbero in tempo a terminare la loro esecuzione. Ricordiamoci sempre di questo aspetto, se il main termina si trascina tutte le altre procedure eventualmente sospese, potete, ad esempio, provare, ad accorciare il tempo proposto alla riga 13 e vedere cosa succede.
Un altro esempio, forse ancora più semplice, estratto direttamente dal sito ufficiale (precisamente lo trovate qua) è il seguente:

  Esempio 15.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"time"
)

func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}

func main() {
go say("world")
say("hello")
}

Il cui output potrebbe essere simile a questo:

world
hello
world
hello
hello
world
hello
world
world
hello

Mentre eliminando la keyword go alla riga 16 avremmo (vi risparmio un po' di fatica e ve lo mostro io)

world
world
world
world
world
hello
hello
hello
hello
hello

Questa è "concorrenza". Il fermo di una funzione permette ad un'altra di proseguire nel proprio funzionamento. E' diverso, come è evidente, dal parallelismo. Per quat'ultimo, come detto, bisogna lavorare su GOMAXPROCS ma ne parleremo a suo tempo, in questi paragrafi vogliamo concentrarci sulla concorrenza "leggera" permessa dalle Goroutines.