Mutexes
fmt syncIn the previous example we saw how to manage simple counter state using atomic operations. For more complex state we can use a mutex to safely access data across multiple goroutines.
package main
import (
"fmt"
"sync"
)Container holds a map of counters; since we want to update it concurrently from multiple goroutines, we add a Mutex to synchronize access. Note that mutexes must not be copied, so if this struct is passed around, it should be done by pointer.
type Container struct {
mu sync.Mutex
counters map[string]int
}Lock the mutex before accessing counters; unlock it at the end of the function using a defer statement.
func (c *Container) inc(name string) {
c.mu.Lock()
defer c.mu.Unlock()
c.counters[name]++
}An RWMutex allows many readers at once, or a single writer.
type Config struct {
mu sync.RWMutex
vals map[string]string
}
func (c *Config) Get(k string) string {
c.mu.RLock()
defer c.mu.RUnlock()
return c.vals[k]
}
func main() {Note that the zero value of a mutex is usable as-is, so no initialization is required here.
c := Container{
counters: map[string]int{"a": 0, "b": 0},
}
var wg sync.WaitGroupThis function increments a named counter in a loop.
doIncrement := func(name string, n int) {
for range n {
c.inc(name)
}
}Run several goroutines concurrently; note that they all access the same Container, and two of them access the same counter.
wg.Go(func() { doIncrement("a", 10000) })
wg.Go(func() { doIncrement("a", 10000) })
wg.Go(func() { doIncrement("b", 10000) })Wait for the goroutines to finish.
wg.Wait()
fmt.Println(c.counters)
cfg := &Config{vals: map[string]string{"env": "prod"}}
fmt.Println(cfg.Get("env"))sync.Map is a map that is safe for concurrent use without an extra mutex. A plain map plus a mutex is still the usual choice when the lock can cover a larger update.
var scores sync.Map
scores.Store("a", 1)
scores.Store("a", 2)
v, ok := scores.Load("a")
fmt.Println("sync map:", v, ok)Cond waits for a condition protected by a mutex. Wait unlocks while the goroutine sleeps and locks again before it returns.
var mu sync.Mutex
var queue []int
cond := sync.NewCond(&mu)
mu.Lock()
go func() {
mu.Lock()
queue = append(queue, 1)
cond.Signal()
mu.Unlock()
}()
for len(queue) == 0 {
cond.Wait()
}
fmt.Println("cond:", queue[0])
mu.Unlock()
}$ go run ./48_mutexes map[a:20000 b:10000] prod sync map: 2 true cond: 1