Goroutines

Go · Practical · 10 min

Goroutines are the foundation of every concurrent Go program, from servers to pipelines.

A goroutine is a function running concurrently with the rest of the program. You start one by putting go before a call: go handle(conn). The call returns immediately; the function runs independently.

Goroutines are cheap. Each starts with a small stack (a few kilobytes) that grows as needed, and the Go runtime multiplexes them onto a handful of OS threads. Running thousands — even hundreds of thousands — is normal. A web server typically uses one goroutine per request.

Things to keep in mind:

  • When main returns, the program exits, killing any goroutines still running. You must explicitly wait for work you care about, using channels or sync.WaitGroup (coming next).
  • go discards the function's return values. Results come back through channels or shared, synchronised variables.
  • Goroutines share memory. If two of them access the same variable and at least one writes, you need synchronisation, or you have a data race.
  • Since Go 1.22, loop variables are per-iteration, so for _, url := range urls { go fetch(url) } and closures capturing url are safe. In older code you'll see url := url copies for this reason.

Every goroutine you start should have a clear way to finish. A goroutine blocked forever on a channel nobody will send to is a goroutine leak: it holds its memory until the process exits. Think about who stops each goroutine before you write go.

time.Sleep to "wait for goroutines" only appears in toy examples; real code synchronises.

Example

package main

import (
	"fmt"
	"time"
)

func worker(id int, done chan<- string) {
	time.Sleep(time.Duration(id) * 10 * time.Millisecond) // simulate work
	done <- fmt.Sprintf("worker %d finished", id)
}

func main() {
	done := make(chan string)
	for id := 1; id <= 3; id++ {
		go worker(id, done) // returns immediately
	}
	for range 3 {
		fmt.Println(<-done) // wait for each result
	}
}

Remember

  • go f() starts f concurrently and returns immediately
  • Goroutines are cheap, but the program exits when main returns
  • Every goroutine needs a defined way to finish

Watch out for

  • Starting goroutines in main and returning before they finish, so their work silently never happens.

Check yourself

  1. main starts `go fmt.Println("hi")` and then returns immediately. What is printed?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: go.dev/ref/spec#Go_statements