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.