Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 21

    Goroutines

    A goroutine is a lightweight thread managed by the Go runtime — start one with the go keyword.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    A goroutine is a lightweight thread managed by the Go runtime — start one with the go keyword. Goroutines multiplex onto OS threads (M:N scheduling), enabling millions of concurrent tasks with kilobytes of stack each.

    Goroutines power HTTP servers (one per request), worker pools, background jobs, and streaming pipelines. Without synchronization (channels, WaitGroup, mutex), shared state races corrupt data — interviewers always probe this gap.

    This lesson covers spawning goroutines, the GOMAXPROCS setting, and common patterns for graceful shutdown with context cancellation.

    The story

    Google's gRPC servers handle each incoming RPC in its own goroutine — lightweight green threads scheduled by the Go runtime across OS threads. A Docker registry mirror spawns goroutines per layer download, saturating network bandwidth without the memory overhead of one OS thread per connection.

    Goroutines communicate via channels (next lesson), but launching them is trivial: go processRequest(req) — the concurrency model that makes Go the default language for cloud-native infrastructure.

    Understanding the topic

    Key concepts

    • go func() starts goroutine — non-blocking, runs concurrently.
    • Main goroutine exiting kills all goroutines — wait before exit.
    • runtime.GOMAXPROCS sets OS thread count; defaults to CPU cores.
    • Goroutine stack starts ~2KB, grows dynamically.
    • Closure captures variables by reference — mind loop vars (fixed Go 1.22).
    • Leak: goroutine blocked forever on channel — always plan exit path.
    text
    flowchart TB
    Main[main goroutine] --> G1[go worker 1]
    Main --> G2[go worker 2]
    G1 --> Done[sync.WaitGroup]
    G2 --> Done

    Step-by-step explanation

    1. go statement schedules function on runtime scheduler.
    2. Scheduler assigns goroutines to GOMAXPROCS logical processors.
    3. Blocking syscall may spawn additional OS thread.
    4. Main returns → program exits regardless of background goroutines.
    5. Use sync.WaitGroup or channel to synchronize completion.
    6. context.Context signals cancellation across goroutine tree.

    Practical code example

    Worker pool spawning goroutines with WaitGroup and context:

    go
    package main
    import (
    "context"
    "fmt"
    "sync"
    "time"
    )
    func worker(ctx context.Context, id int, jobs <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
    select {
    case <-ctx.Done():
    return
    case job, ok := <-jobs:
    if !ok {
    return
    }
    fmt.Printf("worker %d processed job %d\n", id, job)
    time.Sleep(50 * time.Millisecond)
    }
    }
    }
    func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
    defer cancel()
    jobs := make(chan int, 10)
    var wg sync.WaitGroup
    for i := 1; i <= 3; i++ {
    wg.Add(1)
    go worker(ctx, i, jobs, &wg)
    }
    for j := 1; j <= 5; j++ {
    jobs <- j
    }
    close(jobs)
    wg.Wait()
    }

    Line-by-line code explanation

    • go handleRequest(conn) starts a new goroutine — non-blocking, returns immediately.
    • runtime.NumGoroutine() reports active goroutines — useful in leak diagnostics.
    • sync.WaitGroup (covered later) waits for a batch of goroutines to finish.
    • goroutine stacks start small (~2 KB) and grow dynamically — cheaper than OS threads.
    • closure capture — loop variables in goroutines need careful copying (Go 1.22+ fixes loop var semantics).
    • panic in goroutine crashes the whole program unless recovered — always handle errors.
    • GOMAXPROCS controls parallelism across CPU cores — defaults to runtime.NumCPU().
    • main exits when main goroutine finishes — background goroutines are killed unless waited on.

    Key takeaway: Always provide cancellation path. close(jobs) signals no more work. WaitGroup ensures main waits for workers.

    Real-world use

    Where you'll use this in production

    • net/http handles each request in a goroutine automatically.
    • Background email/notification senders decoupled from API response.
    • Parallel health checks aggregating microservice status.
    • Fan-out/fan-in data processing pipelines.

    Best practices

    • Every goroutine must have a planned exit (context, close, timeout).
    • Use WaitGroup or errgroup for coordinated shutdown.
    • Don't spawn unbounded goroutines — use worker pools with limits.
    • Pass dependencies as params, not global vars.
    • Monitor goroutine count in production via runtime/metrics or pprof.

    Common mistakes

    • Main exits before goroutines finish — incomplete work.
    • Goroutine leak on blocked channel send/receive.
    • Unbounded go per incoming request without backpressure.
    • Race on shared variables without sync or channels.
    • Capturing loop variable in closure pre-1.22.

    Advanced interview questions

    Q1BeginnerGoroutine vs OS thread?
    Goroutines are lightweight, multiplexed on threads by Go scheduler — cheaper to create.
    Q2BeginnerWhat happens if main returns while goroutines run?
    Program exits immediately — goroutines terminated.
    Q3IntermediateGOMAXPROCS effect?
    Sets max OS threads executing Go code simultaneously; default NumCPU.
    Q4IntermediateDetect goroutine leak?
    pprof goroutine profile; monitor count; look for blocked channel/mutex stacks.
    Q5AdvancedDesign worker pool processing 10K jobs/sec.
    Fixed N workers; buffered job channel; context cancel; metrics on queue depth; reject when full.

    Summary

    go keyword launches lightweight concurrent goroutines. Main must wait for workers — WaitGroup, channels, or errgroup. Always plan goroutine exit to prevent leaks. Use worker pools instead of unbounded goroutine spawning. Next lesson: channels — communicating between goroutines.

    Ready to mark this lesson complete?Track your journey across the entire course.