Module lessons (1/5)
Goroutines: lightweight parallelism
A goroutine is a unit of concurrent execution managed by the Go
runtime. You launch one with the go keyword in front of a function
call:
go greet("Anna") // chiamata a funzione esistente
go func() { // funzione anonima
fmt.Println("hi")
}()The go statement returns immediately: the goroutine starts in
parallel and the caller keeps going.
Why they are "lightweight"
- Initial stack ~2KB (grows dynamically, up to hundreds of MB if needed).
- Multiplexed onto a small number of OS threads by the runtime (
GOMAXPROCS). - Thousands/millions of goroutines are routine; with OS threads they would be impossible.
Conceptually it is "concurrent fire-and-forget", but with all the
idiomatic tools to coordinate (channels, sync.WaitGroup,
context).
main does not wait
func main() {
go func() { fmt.Println("ciao dalla goroutine") }()
// main esce subito: niente garanzia che la goroutine completi
}When main returns, the process terminates: goroutines do not get
to "finish their work", they are simply killed together with the
process. Explicit synchronization is required.
Synchronization: a preview
The three tools you will see in the next lessons:
// 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 propagatiThe "classic" closure-in-loop bug
Up to Go 1.21 this was a frequent gotcha:
for i := 0; i < 3; i++ {
go func() { fmt.Println(i) }() // stampa "3 3 3" su versioni vecchie
}From Go 1.22 the for variable is freshly created on each
iteration, so the bug is fixed by default. In older codebases you will
still see the fix pattern:
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 != OS thread
| Aspect | Goroutine | OS thread |
|---|---|---|
| Initial stack | ~2 KB | 1–8 MB (allocated at start) |
| Creation | nanoseconds | microseconds |
| Scheduling | Go runtime (cooperative) | kernel (preemptive) |
| Practical count | 100K+ | a few thousand |
A single OS thread can host many goroutines. The runtime moves goroutines across threads when needed.
Try it
Launch the greet function as a goroutine and wait 100ms with time.Sleep before exiting main.
Show hint
`go` in front of a function call launches the goroutine.
Solution available after 3 attempts
Launch an anonymous function as a goroutine that prints 'hi'.
Show hint
An anonymous function is called with a trailing `()`: `go func(){...}()`.
Solution available after 3 attempts
What happens if `main` exits before a goroutine completes?
func main() {
go work()
// main ritorna immediatamente
}Recap
go f()launches f() concurrently; it returns immediately.- Goroutine = ~2 KB initial, multiplexed onto a few OS threads.
maindoes not wait: you need synchronization (channel/WaitGroup/context).time.Sleepis only didactic, never for sync in production.- From Go 1.22 the
forvariable is "fresh" on each iteration (no more "3 3 3" bug).