Go by Example

Goroutines

Lightweight concurrent functions launched with the go keyword - the foundation of Go's concurrency model.

A goroutine is a function executing concurrently with other goroutines in the same address space. The go keyword launches one. Goroutines start with a small stack (around 2-8 KB) that grows as needed, so spawning thousands is practical.

Prefix any function call with go to run it concurrently. The calling goroutine continues immediately without waiting. If main returns before the goroutine finishes, the goroutine is killed.

package main
 
import (
    "fmt"
    "time"
)
 
func say(s string) {
    for i := 0; i < 3; i++ {
        time.Sleep(50 * time.Millisecond)
        fmt.Println(s)
    }
}
 
func main() {
    go say("goroutine") // runs concurrently
    say("main")         // runs in the main goroutine
}

Anonymous functions work too - useful for short, in-place concurrent tasks:

go func(msg string) {
    fmt.Println(msg)
}("hello from goroutine")

The goroutine scheduler is co-operative with preemption points at function calls, channel operations, and system calls. GOMAXPROCS (default: number of CPU cores) controls how many goroutines run in parallel on OS threads. Use a channel or sync.WaitGroup to wait for goroutines to finish:

done := make(chan struct{})
go func() {
    fmt.Println("working...")
    close(done)
}()
<-done // blocks until goroutine closes the channel

In production

Goroutines are cheap but not free. Launching one per inbound request in a high-QPS service can create millions of live goroutines under load, each holding a stack frame while blocked on I/O. Use a worker pool or semaphore to bound concurrency when processing untrusted input or calling rate-limited services. The main goroutine exiting kills all goroutines immediately - use sync.WaitGroup or a done channel to ensure cleanup runs before exit.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free