Skip to main content
eLearner.app
Module 7 · Lesson 1 of 531/50 in the course~12 min
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
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

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

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

The "classic" closure-in-loop bug

Up to Go 1.21 this was a frequent gotcha:

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

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 != OS thread

AspectGoroutineOS thread
Initial stack~2 KB1–8 MB (allocated at start)
Creationnanosecondsmicroseconds
SchedulingGo runtime (cooperative)kernel (preemptive)
Practical count100K+a few thousand

A single OS thread can host many goroutines. The runtime moves goroutines across threads when needed.

Try it

Exercise#go.m7.l1.e1
Attempts: 0Loading…

Launch the greet function as a goroutine and wait 100ms with time.Sleep before exiting main.

Loading editor…
Show hint

`go` in front of a function call launches the goroutine.

Solution available after 3 attempts

Exercise#go.m7.l1.e2
Attempts: 0Loading…

Launch an anonymous function as a goroutine that prints 'hi'.

Loading editor…
Show hint

An anonymous function is called with a trailing `()`: `go func(){...}()`.

Solution available after 3 attempts

Quiz#go.m7.l1.e3
Ready

What happens if `main` exits before a goroutine completes?

Go
func main() {
  go work()
  // main ritorna immediatamente
}
Answer options

Recap

  • go f() launches f() concurrently; it returns immediately.
  • Goroutine = ~2 KB initial, multiplexed onto a few OS threads.
  • main does not wait: you need synchronization (channel/WaitGroup/context).
  • time.Sleep is only didactic, never for sync in production.
  • From Go 1.22 the for variable is "fresh" on each iteration (no more "3 3 3" bug).