Vai al contenuto
eLearner.app
Modulo 7 · Lezione 1 di 531/50 nel corso~12 min
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
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

Go
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:

Go
// 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 propagati

Il bug "classico" delle closure in loop

Fino a Go 1.21 questo era un trabocchetto frequente:

Go
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:

Go
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

AspettoGoroutineThread OS
Stack iniziale~2 KB1–8 MB (allocato all'avvio)
Creazionenanosecondimicrosecondi
Schedulingruntime Go (cooperativo)kernel (preemptivo)
Numero pratico100K+qualche migliaio

Un thread OS può ospitare molte goroutine. Il runtime sposta le goroutine fra thread quando necessario.

Prova tu

Esercizio#go.m7.l1.e1
Tentativi: 0Caricamento…

Lancia in goroutine la funzione greet e attendi 100ms con time.Sleep prima di uscire dal main.

Caricamento editor…
Mostra suggerimento

`go` prima della chiamata di funzione lancia la goroutine.

Soluzione disponibile dopo 3 tentativi

Esercizio#go.m7.l1.e2
Tentativi: 0Caricamento…

Lancia una funzione anonima in goroutine che stampa 'hi'.

Caricamento editor…
Mostra suggerimento

Una funzione anonima si chiama con `()` finale: `go func(){...}()`.

Soluzione disponibile dopo 3 tentativi

Quiz#go.m7.l1.e3
Pronto

Cosa succede se `main` esce prima che una goroutine completi?

Go
func main() {
  go work()
  // main ritorna immediatamente
}
Opzioni di risposta

Recap

  • go f() lancia f() in concorrenza; ritorna subito.
  • Goroutine = ~2 KB iniziali, multiplexate su pochi thread OS.
  • main non 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").