Dev Skills

Goroutines

Start ten thousand concurrent things with one keyword.

Topic 39 of 98 · 5 min to read · 50 min to build
After this topic you can
  • Write a program that starts several goroutines and waits for all of them with sync.WaitGroup.
  • Predict what happens when main returns before its goroutines finish.
  • Fix a WaitGroup bug where Add is called inside the goroutine instead of before it.

What is a goroutine?

A goroutine is a function that runs at the same time as the rest of your program. You start one by writing go in front of a function call. That is the whole syntax.

They are very cheap. Starting one costs far less than a millisecond and a few kilobytes of memory, so a program can run thousands of them without trouble.

goroutine.go
go doSomething() // returns immediately, doSomething runs at the same time
go func() {
fmt.Println("in a goroutine")
}()
Run it a few times: "hello" and "world" are mixed together because both functions run at once. When `main` returns, the program exits and every goroutine stops with it. Sleeping is only a demo; the WaitGroup below is the proper way to wait.

How do you wait for goroutines to finish? (WaitGroup)

waitgroup.go
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // count up BEFORE starting the goroutine
go func() {
defer wg.Done() // count down when it finishes, whatever happens
fmt.Println(i)
}()
}
wg.Wait() // blocks until the count reaches zero
Call `Add` before `go`, never inside the goroutine. Otherwise `Wait` can run before the count goes up and return immediately. Printing `i` directly is safe since Go 1.22, because each loop iteration gets its own `i`. On older versions all five goroutines shared one variable and could print 5 five times.

How do goroutines work underneath? (optional)

A goroutine is scheduled by the Go runtime onto a few OS threads, not given a thread of its own. You can skip this on a first read. A goroutine is managed by the Go runtime, not the operating system. It starts with a small stack (2KB minimum) that grows as needed. An OS thread reserves a fixed stack of a megabyte or more (8MB by default on Linux) and needs a system call to create. That is why a Go server can run a hundred thousand goroutines, where a hundred thousand OS threads would strain the machine.

Go runs many goroutines on a few OS threads. When a goroutine blocks — on a channel, a mutex, a network read, a time.Sleep — the runtime parks it and runs another one on that thread. No thread sits idle waiting.

This is what makes Go servers simple to write. You write plain blocking code that reads like a script, and the runtime handles the waiting for you. There are no callbacks and no async/await.

Try it yourself

press Run — this executes on the Go Playground
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // BEFORE the go statement
go func(n int) {
defer wg.Done()
time.Sleep(time.Duration(n) * 10 * time.Millisecond)
fmt.Printf("worker %d done\n", n)
}(i)
}
wg.Wait() // main waits here until all five have called Done
fmt.Println("all workers finished")
}
Five goroutines and a WaitGroup. Getting values back out of goroutines is the next lesson, Channels and select.
The short version
  • go f() starts a goroutine. It starts with a few KB of stack, so thousands are normal.
  • The runtime multiplexes them onto a few OS threads and parks them when they block.
  • You write plain blocking code and the runtime handles the waiting, with no callbacks.
  • WaitGroup waits for a known number. Add before go, defer wg.Done() inside.
Remember: When main returns the program exits, and every goroutine dies with it.
Build this before moving on
Start five goroutines that print their index, with and without a WaitGroup. Run each version several times.
Coding challenge · checked by tests
Write SumChunks(nums, n): split nums into n equal chunks, sum each chunk in its own goroutine, and write the sum into slot i of a slice you made before starting them. Use a sync.WaitGroup to wait. len(nums) is always a multiple of n. Example: SumChunks([1 2 3 4 5 6], 3) returns [3 7 11].

Tab indents. Press Esc, then Tab, to leave the editor.

Then try these in your own editor
  1. 1Call wg.Add(1) inside the goroutine instead of before it, and see what breaks.
  2. 2Start 100,000 goroutines that sleep, and watch the memory.
  3. 3Start a goroutine and return from main immediately. Does it run?
  4. 4Predict the output before you run it: for i := 0; i < 3; i++ { go fmt.Print(i, " ") } followed by time.Sleep(100 * time.Millisecond).
Check yourselfTake the Concurrency test