Goroutines
Start ten thousand concurrent things with one keyword.
- Write a program that starts several goroutines and waits for all of them with
sync.WaitGroup. - Predict what happens when
mainreturns before its goroutines finish. - Fix a WaitGroup bug where
Addis 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.
go doSomething() // returns immediately, doSomething runs at the same timego func() { fmt.Println("in a goroutine")}()How do you wait for goroutines to finish? (WaitGroup)
var wg sync.WaitGroupfor 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 zeroHow 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
package mainimport ( "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")}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.
WaitGroupwaits for a known number.Addbeforego,defer wg.Done()inside.
main returns the program exits, and every goroutine dies with it.WaitGroup. Run each version several times.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.
- 1Call
wg.Add(1)inside the goroutine instead of before it, and see what breaks. - 2Start 100,000 goroutines that sleep, and watch the memory.
- 3Start a goroutine and return from main immediately. Does it run?
- 4Predict the output before you run it:
for i := 0; i < 3; i++ { go fmt.Print(i, " ") }followed bytime.Sleep(100 * time.Millisecond).