diff --git a/examples/mutexes/mutexes.go b/examples/mutexes/mutexes.go index 59e736d..fce6019 100644 --- a/examples/mutexes/mutexes.go +++ b/examples/mutexes/mutexes.go @@ -7,79 +7,57 @@ package main import ( "fmt" - "math/rand" "sync" - "sync/atomic" - "time" ) -func main() { - - // For our example the `state` will be a map. - var state = make(map[int]int) - - // This `mutex` will synchronize access to `state`. - var mutex = &sync.Mutex{} - - // We'll keep track of how many read and write - // operations we do. - var readOps uint64 - var writeOps uint64 - - // Here we start 100 goroutines to execute repeated - // reads against the state, once per millisecond in - // each goroutine. - for r := 0; r < 100; r++ { - go func() { - total := 0 - for { - - // For each read we pick a key to access, - // `Lock()` the `mutex` to ensure - // exclusive access to the `state`, read - // the value at the chosen key, - // `Unlock()` the mutex, and increment - // the `readOps` count. - key := rand.Intn(5) - mutex.Lock() - total += state[key] - mutex.Unlock() - atomic.AddUint64(&readOps, 1) - - // Wait a bit between reads. - time.Sleep(time.Millisecond) - } - }() - } - - // We'll also start 10 goroutines to simulate writes, - // using the same pattern we did for reads. - for w := 0; w < 10; w++ { - go func() { - for { - key := rand.Intn(5) - val := rand.Intn(100) - mutex.Lock() - state[key] = val - mutex.Unlock() - atomic.AddUint64(&writeOps, 1) - time.Sleep(time.Millisecond) - } - }() - } - - // Let the 10 goroutines work on the `state` and - // `mutex` for a second. - time.Sleep(time.Second) - - // Take and report final operation counts. - readOpsFinal := atomic.LoadUint64(&readOps) - fmt.Println("readOps:", readOpsFinal) - writeOpsFinal := atomic.LoadUint64(&writeOps) - fmt.Println("writeOps:", writeOpsFinal) - - // With a final lock of `state`, show how it ended up. - mutex.Lock() - fmt.Println("state:", state) - mutex.Unlock() +// Container holds a map of counters; since we want to +// update it concurrently from multiple goroutines, we +// add a `Mutex` to synchronize access. The mutex is +// _embedded_ in this `struct`; this is idiomatic in Go. +// Note that mutexes must not be copied, so if this +// `struct` is passed around, it should be done by +// pointer. +type Container struct { + sync.Mutex + counters map[string]int +} + +func (c *Container) inc(name string) { + // Lock the mutex before accessing `counters`; unlock + // it at the end of the function using a [defer](defer) + // statement. Since the mutex is embedded into + // `Container`, we can call the mutex's methods like + // `Lock` directly on `c`. + c.Lock() + defer c.Unlock() + c.counters[name]++ +} + +func main() { + c := Container{ + counters: map[string]int{"a": 0, "b": 0}, + } + + var wg sync.WaitGroup + + // This function increments a named counter + // in a loop. + doIncrement := func(name string, n int) { + for i := 0; i < n; i++ { + c.inc(name) + } + wg.Done() + } + + // Run several goroutines concurrently; note + // that they all access the same `Container`, + // and two of them access the same counter. + wg.Add(3) + go doIncrement("a", 10000) + go doIncrement("a", 10000) + go doIncrement("b", 10000) + + // Wait a for the goroutines to finish + wg.Wait() + fmt.Println(c.counters) } diff --git a/examples/mutexes/mutexes.hash b/examples/mutexes/mutexes.hash index 4455e8c..70c6f65 100644 --- a/examples/mutexes/mutexes.hash +++ b/examples/mutexes/mutexes.hash @@ -1,2 +1,2 @@ -253b089b8145fc57a90ae4024346b6db2ec1659b -CHCDredHCOz +07179e54fb3466ab01ac8aa9550feb213a206785 +i50fhu4l-n0 diff --git a/examples/mutexes/mutexes.sh b/examples/mutexes/mutexes.sh index 185f4fa..d379c1f 100644 --- a/examples/mutexes/mutexes.sh +++ b/examples/mutexes/mutexes.sh @@ -1,10 +1,7 @@ -# Running the program shows that we executed about -# 90,000 total operations against our `mutex`-synchronized -# `state`. +# Running the program shows that the counters +# updated as expected. $ go run mutexes.go -readOps: 83285 -writeOps: 8320 -state: map[1:97 4:53 0:33 2:15 3:2] +map[a:20000 b:10000] # Next we'll look at implementing this same state # management task using only goroutines and channels. diff --git a/public/mutexes b/public/mutexes index 0624b0a..4fd5e95 100644 --- a/public/mutexes +++ b/public/mutexes @@ -44,7 +44,7 @@ to safely access data across multiple goroutines.
package main
import ( "fmt" - "math/rand" "sync" - "sync/atomic" - "time" )+
Container holds a map of counters; since we want to
+update it concurrently from multiple goroutines, we
+add a Mutex
to synchronize access. The mutex is
+embedded in this struct
; this is idiomatic in Go.
+Note that mutexes must not be copied, so if this
+struct
is passed around, it should be done by
+pointer.
+type Container struct { + sync.Mutex + counters map[string]int +} ++
Lock the mutex before accessing counters
; unlock
+it at the end of the function using a defer
+statement. Since the mutex is embedded into
+Container
, we can call the mutex’s methods like
+Lock
directly on c
.
func (c *Container) inc(name string) { ++
c.Lock() + defer c.Unlock() + c.counters[name]++ +} ++
func main() { --
For our example the state
will be a map.
- var state = make(map[int]int) --
This mutex
will synchronize access to state
.
- var mutex = &sync.Mutex{} --
We’ll keep track of how many read and write -operations we do.
- -- var readOps uint64 - var writeOps uint64 --
Here we start 100 goroutines to execute repeated -reads against the state, once per millisecond in -each goroutine.
- -- for r := 0; r < 100; r++ { - go func() { - total := 0 - for { --
For each read we pick a key to access,
-Lock()
the mutex
to ensure
-exclusive access to the state
, read
-the value at the chosen key,
-Unlock()
the mutex, and increment
-the readOps
count.
- key := rand.Intn(5) - mutex.Lock() - total += state[key] - mutex.Unlock() - atomic.AddUint64(&readOps, 1) --
Wait a bit between reads.
- -- time.Sleep(time.Millisecond) - } - }() + c := Container{ + counters: map[string]int{"a": 0, "b": 0}, }
readOps
count.
We’ll also start 10 goroutines to simulate writes, -using the same pattern we did for reads.
+ +var wg sync.WaitGroup ++
This function increments a named counter +in a loop.
- for w := 0; w < 10; w++ { - go func() { - for { - key := rand.Intn(5) - val := rand.Intn(100) - mutex.Lock() - state[key] = val - mutex.Unlock() - atomic.AddUint64(&writeOps, 1) - time.Sleep(time.Millisecond) - } - }() + doIncrement := func(name string, n int) { + for i := 0; i < n; i++ { + c.inc(name) + } + wg.Done() }
Let the 10 goroutines work on the state
and
-mutex
for a second.
Run several goroutines concurrently; note
+that they all access the same Container
,
+and two of them access the same counter.
- time.Sleep(time.Second) + wg.Add(3) + go doIncrement("a", 10000) + go doIncrement("a", 10000) + go doIncrement("b", 10000)
Take and report final operation counts.
- -- readOpsFinal := atomic.LoadUint64(&readOps) - fmt.Println("readOps:", readOpsFinal) - writeOpsFinal := atomic.LoadUint64(&writeOps) - fmt.Println("writeOps:", writeOpsFinal) --
With a final lock of state
, show how it ended up.
Wait a for the goroutines to finish
- mutex.Lock() - fmt.Println("state:", state) - mutex.Unlock() + wg.Wait() + fmt.Println(c.counters) }
Running the program shows that we executed about
-90,000 total operations against our mutex
-synchronized
-state
.
Running the program shows that the counters +updated as expected.
$ go run mutexes.go -readOps: 83285 -writeOps: 8320 -state: map[1:97 4:53 0:33 2:15 3:2]+map[a:20000 b:10000]