Go by Example

Stateful Goroutines

Confine mutable state to a single goroutine and expose it through channels to avoid data races by design.

A stateful goroutine owns mutable data and handles every request to read or change it. Since only that goroutine accesses the data, callers do not need a mutex for it.

The worker owns the map. Callers send a request and wait for its reply; a wait group confirms the worker exits after the request channel closes.

package main
 
import (
    "fmt"
    "sync"
)
 
type request struct {
    key   string
    value int
    reply chan int
}
 
func main() {
    requests := make(chan request)
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        values := make(map[string]int)
        for req := range requests {
            values[req.key] = req.value
            req.reply <- values[req.key]
        }
    }()
    req := request{"jobs", 4, make(chan int)}
    requests <- req
    fmt.Println(<-req.reply)
    close(requests)
    wg.Wait()
}

In production

This pattern is useful when updates follow a sequence or protocol. It adds channel traffic and can limit throughput, so a mutex is often simpler for basic shared state. Keep every access behind the owner goroutine and define how it shuts down.

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