Le goroutine sono thread di esecuzione leggeri che possono essere facilmente creati e gestiti dal runtime di Go. Le goroutine sono simili ai thread di altri linguaggi, ma sono molto meno costose da creare e gestire, il che le rende ideali per la programmazione concorrente. Ecco un esempio di come creare una goroutine in Go:
Goroutine
package main
import "fmt"
func main() {
go fmt.Println("Hello from goroutine 1")
go fmt.Println("Hello from goroutine 2")
}Questo codice crea una nuova goroutine che stampa un messaggio sulla console. La keyword go indica che la funzione deve essere eseguita in una nuova goroutine. Poiché le goroutine sono concorrenti, la funzione main continuerà a eseguire mentre la goroutine è in esecuzione.
Channel
I channel sono un'altra primitiva di concorrenza fondamentale in Go. I channel forniscono un modo per le goroutine di comunicare e sincronizzarsi tra loro. Usando i channel, le goroutine possono inviarsi e riceversi valori, permettendo loro di coordinarsi e condividere dati. Ecco un esempio di come usare i channel in Go:
package main
import "fmt"
func main() {
// Crea un channel per inviare e ricevere stringhe.
c := make(chan string)
// Crea due goroutine che inviano messaggi al channel.
go func() { c <- "Hello from goroutine 1" }()
go func() { c <- "Hello from goroutine 2" }()
// Legge i messaggi dal channel e li stampa sulla console.
fmt.Println(<-c)
fmt.Println(<-c)
}Questo codice crea un nuovo channel e una goroutine che invia un valore sul channel. La funzione main riceve il valore dal channel e lo stampa sulla console. I channel sono uno strumento potente per coordinare e sincronizzare le goroutine.
Select
L'istruzione select consente a una goroutine di attendere che più channel siano pronti, quindi eseguire un blocco di codice in base al channel che risulta pronto per primo. Ecco un esempio di come usare select per implementare un semplice timer concorrente:
package main
import "fmt"
import "time"
func main() {
// Crea due channel: uno per inviare un segnale dopo 1 secondo,
// e uno per inviare un segnale dopo 2 secondi.
c1 := time.After(1 * time.Second)
c2 := time.After(2 * time.Second)
// Usa select per attendere che uno dei channel sia pronto.
select {
case <-c1:
fmt.Println("1 second elapsed")
case <-c2:
fmt.Println("2 seconds elapsed")
}
}Mutex
Per evitare che l'accesso concorrente a dati condivisi provochi race condition e altri bug, Go mette a disposizione il tipo mutex (abbreviazione di mutual exclusion), utilizzabile per proteggere sezioni critiche del codice. Ecco un esempio di come usare un mutex per incrementare in modo sicuro una variabile contatore condivisa:
package main
import "fmt"
import "sync"
func main() {
// Crea un mutex per proteggere la variabile contatore.
var mu sync.Mutex
// Crea una variabile contatore e inizializzala a 0.
var counter int
// Crea 10 goroutine che incrementano il contatore in modo concorrente.
for i := 0; i < 10; i++ {
go func() {
mu.Lock()
defer mu.Unlock()
counter++
}()
}
// Attende che tutte le goroutine terminino, poi stampa il valore finale del contatore.
time.Sleep(1 * time.Second)
fmt.Println(counter)
}Wait Group
Il pacchetto sync in Go fornisce strumenti aggiuntivi per lavorare con la concorrenza, tra cui WaitGroup per coordinare la terminazione di più goroutine, Once per garantire che un pezzo di codice venga eseguito una sola volta, e Pool per gestire e riutilizzare un pool di risorse.
Ecco un esempio di come usare il tipo WaitGroup del pacchetto sync per attendere che un gruppo di goroutine termini:
package main
import "fmt"
import "sync"
func main() {
// Crea un WaitGroup per attendere che le goroutine terminino.
var wg sync.WaitGroup
// Avvia 10 goroutine che stampano messaggi in modo concorrente.
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("Hello from goroutine")
wg.Done()
}()
}
// Attende che le goroutine terminino.
wg.Wait()
}Riepilogo
In conclusione, la concorrenza è un concetto potente e importante in Go, e il linguaggio offre un ricco set di strumenti per lavorarci, tra cui goroutine, channel, istruzioni select, il pacchetto mutex e il pacchetto sync. Usando questi strumenti in modo efficace, i programmatori Go possono scrivere programmi concorrenti che siano efficienti, scalabili e facili da comprendere.
lucavallin

