Lezioni del modulo (1/5)
Goroutine: parallelismo leggero
Una goroutine è un'unità di esecuzione concorrente gestita dal runtime
Go. Si lancia con la keyword go davanti a una chiamata di funzione:
go greet("Anna") // chiamata a funzione esistente
go func() { // funzione anonima
fmt.Println("hi")
}()L'istruzione go ritorna subito: la goroutine parte in parallelo e
il chiamante continua.
Perché sono "leggere"
- Stack iniziale ~2KB (cresce dinamicamente, fino a centinaia di MB se serve).
- Multiplexate su un numero ridotto di thread OS dal runtime (
GOMAXPROCS). - Migliaia/milioni di goroutine sono normali; con thread OS sarebbero impossibili.
Concettualmente è "fire-and-forget concorrente", ma con tutti gli
strumenti idiomatici per coordinare (channel, sync.WaitGroup,
context).
Il main non aspetta
func main() {
go func() { fmt.Println("ciao dalla goroutine") }()
// main esce subito: niente garanzia che la goroutine completi
}Quando main ritorna, il processo termina: le goroutine non
finiscono "il loro lavoro", vengono semplicemente uccise insieme al
processo. Serve sincronizzazione esplicita.
Sincronizzazione: anteprima
I tre strumenti che vedrai nelle prossime lezioni:
// 1) Channel: la goroutine "segnala" via canale
done := make(chan struct{})
go func() {
work()
done <- struct{}{}
}()
<-done // attendi
// 2) sync.WaitGroup: N goroutine
var wg sync.WaitGroup
wg.Add(1)
go func() { defer wg.Done(); work() }()
wg.Wait()
// 3) context: cancellazione/timeout propagatiIl bug "classico" delle closure in loop
Fino a Go 1.21 questo era un trabocchetto frequente:
for i := 0; i < 3; i++ {
go func() { fmt.Println(i) }() // stampa "3 3 3" su versioni vecchie
}Da Go 1.22 la variabile del for è creata fresca per ogni iterazione,
quindi il bug è risolto di default. Sui codebase vecchi vedrai ancora il
pattern di fix:
for i := 0; i < 3; i++ {
i := i // shadow esplicito
go func() { fmt.Println(i) }()
}
// oppure passa come argomento:
for i := 0; i < 3; i++ {
go func(n int) { fmt.Println(n) }(i)
}Goroutine != thread OS
| Aspetto | Goroutine | Thread OS |
|---|---|---|
| Stack iniziale | ~2 KB | 1–8 MB (allocato all'avvio) |
| Creazione | nanosecondi | microsecondi |
| Scheduling | runtime Go (cooperativo) | kernel (preemptivo) |
| Numero pratico | 100K+ | qualche migliaio |
Un thread OS può ospitare molte goroutine. Il runtime sposta le goroutine fra thread quando necessario.
Prova tu
Lancia in goroutine la funzione greet e attendi 100ms con time.Sleep prima di uscire dal main.
Mostra suggerimento
`go` prima della chiamata di funzione lancia la goroutine.
Soluzione disponibile dopo 3 tentativi
Lancia una funzione anonima in goroutine che stampa 'hi'.
Mostra suggerimento
Una funzione anonima si chiama con `()` finale: `go func(){...}()`.
Soluzione disponibile dopo 3 tentativi
Cosa succede se `main` esce prima che una goroutine completi?
func main() {
go work()
// main ritorna immediatamente
}Recap
go f()lancia f() in concorrenza; ritorna subito.- Goroutine = ~2 KB iniziali, multiplexate su pochi thread OS.
mainnon aspetta: serve sincronizzazione (channel/WaitGroup/context).time.Sleepè solo didattico, mai per sync in produzione.- Da Go 1.22 la variabile del
forè "fresh" a ogni iterazione (no più bug "3 3 3").