diff --git a/examples/atomic-counters/atomic-counters.go b/examples/atomic-counters/atomic-counters.go index 682e524..72f2396 100644 --- a/examples/atomic-counters/atomic-counters.go +++ b/examples/atomic-counters/atomic-counters.go @@ -10,7 +10,6 @@ package main import "fmt" import "time" import "sync/atomic" -import "runtime" func main() { @@ -30,8 +29,8 @@ func main() { // `&` syntax. atomic.AddUint64(&ops, 1) - // Allow other goroutines to proceed. - runtime.Gosched() + // Wait a bit between increments. + time.Sleep(time.Millisecond) } }() } diff --git a/examples/atomic-counters/atomic-counters.hash b/examples/atomic-counters/atomic-counters.hash index e1cec67..199e3b1 100644 --- a/examples/atomic-counters/atomic-counters.hash +++ b/examples/atomic-counters/atomic-counters.hash @@ -1,2 +1,2 @@ -ddfef8425eef64cfcb82799c9bddca4bfa9bbd29 -2h8nvrnaHP +ce8821f1f4fd99d554ad6cde52403dd3b69bb70a +8p48eFFxDZ diff --git a/examples/atomic-counters/atomic-counters.sh b/examples/atomic-counters/atomic-counters.sh index 92a26a5..e4523f9 100644 --- a/examples/atomic-counters/atomic-counters.sh +++ b/examples/atomic-counters/atomic-counters.sh @@ -1,7 +1,7 @@ # Running the program shows that we executed about # 40,000 operations. $ go run atomic-counters.go -ops: 40200 +ops: 41419 # Next we'll look at mutexes, another tool for managing # state. diff --git a/examples/mutexes/mutexes.go b/examples/mutexes/mutexes.go index f054494..c2068b2 100644 --- a/examples/mutexes/mutexes.go +++ b/examples/mutexes/mutexes.go @@ -8,7 +8,6 @@ package main import ( "fmt" "math/rand" - "runtime" "sync" "sync/atomic" "time" @@ -22,13 +21,14 @@ func main() { // This `mutex` will synchronize access to `state`. var mutex = &sync.Mutex{} - // To compare the mutex-based approach with another - // we'll see later, `ops` will count how many - // operations we perform against the state. - var ops int64 = 0 + // We'll keep track of how many read and write + // operations we do. + var readOps uint64 = 0 + var writeOps uint64 = 0 // Here we start 100 goroutines to execute repeated - // reads against the state. + // reads against the state, once per millisecond in + // each goroutine. for r := 0; r < 100; r++ { go func() { total := 0 @@ -39,22 +39,15 @@ func main() { // exclusive access to the `state`, read // the value at the chosen key, // `Unlock()` the mutex, and increment - // the `ops` count. + // the `readOps` count. key := rand.Intn(5) mutex.Lock() total += state[key] mutex.Unlock() - atomic.AddInt64(&ops, 1) + atomic.AddUint64(&readOps, 1) - // In order to ensure that this goroutine - // doesn't starve the scheduler, we explicitly - // yield after each operation with - // `runtime.Gosched()`. This yielding is - // handled automatically with e.g. every - // channel operation and for blocking - // calls like `time.Sleep`, but in this - // case we need to do it manually. - runtime.Gosched() + // Wait a bit between reads. + time.Sleep(time.Millisecond) } }() } @@ -69,8 +62,8 @@ func main() { mutex.Lock() state[key] = val mutex.Unlock() - atomic.AddInt64(&ops, 1) - runtime.Gosched() + atomic.AddUint64(&writeOps, 1) + time.Sleep(time.Millisecond) } }() } @@ -79,9 +72,11 @@ func main() { // `mutex` for a second. time.Sleep(time.Second) - // Take and report a final operations count. - opsFinal := atomic.LoadInt64(&ops) - fmt.Println("ops:", opsFinal) + // 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() diff --git a/examples/mutexes/mutexes.hash b/examples/mutexes/mutexes.hash index 79fd377..296215c 100644 --- a/examples/mutexes/mutexes.hash +++ b/examples/mutexes/mutexes.hash @@ -1,2 +1,2 @@ -3ce8467418aa740ea6c930afac3985a943c76311 -kZrod-Rkos +e82356cbb37143862b0a9bbc68856f4b272c4918 +a9Wky7k-Bw diff --git a/examples/mutexes/mutexes.sh b/examples/mutexes/mutexes.sh index 8895533..185f4fa 100644 --- a/examples/mutexes/mutexes.sh +++ b/examples/mutexes/mutexes.sh @@ -1,9 +1,10 @@ # Running the program shows that we executed about -# 3,500,000 operations against our `mutex`-synchronized +# 90,000 total operations against our `mutex`-synchronized # `state`. $ go run mutexes.go -ops: 3598302 -state: map[1:38 4:98 2:23 3:85 0:44] +readOps: 83285 +writeOps: 8320 +state: map[1:97 4:53 0:33 2:15 3:2] # Next we'll look at implementing this same state # management task using only goroutines and channels. diff --git a/examples/stateful-goroutines/stateful-goroutines.go b/examples/stateful-goroutines/stateful-goroutines.go index 0251aa5..eb2ff79 100644 --- a/examples/stateful-goroutines/stateful-goroutines.go +++ b/examples/stateful-goroutines/stateful-goroutines.go @@ -37,7 +37,8 @@ type writeOp struct { func main() { // As before we'll count how many operations we perform. - var ops int64 = 0 + var readOps uint64 = 0 + var writeOps uint64 = 0 // The `reads` and `writes` channels will be used by // other goroutines to issue read and write requests, @@ -80,7 +81,8 @@ func main() { resp: make(chan int)} reads <- read <-read.resp - atomic.AddInt64(&ops, 1) + atomic.AddUint64(&readOps, 1) + time.Sleep(time.Millisecond) } }() } @@ -96,7 +98,8 @@ func main() { resp: make(chan bool)} writes <- write <-write.resp - atomic.AddInt64(&ops, 1) + atomic.AddUint64(&writeOps, 1) + time.Sleep(time.Millisecond) } }() } @@ -104,7 +107,9 @@ func main() { // Let the goroutines work for a second. time.Sleep(time.Second) - // Finally, capture and report the `ops` count. - opsFinal := atomic.LoadInt64(&ops) - fmt.Println("ops:", opsFinal) + // Finally, capture and report the op counts. + readOpsFinal := atomic.LoadUint64(&readOps) + fmt.Println("readOps:", readOpsFinal) + writeOpsFinal := atomic.LoadUint64(&writeOps) + fmt.Println("writeOps:", writeOpsFinal) } diff --git a/examples/stateful-goroutines/stateful-goroutines.hash b/examples/stateful-goroutines/stateful-goroutines.hash index 43724f4..c2233b3 100644 --- a/examples/stateful-goroutines/stateful-goroutines.hash +++ b/examples/stateful-goroutines/stateful-goroutines.hash @@ -1,2 +1,2 @@ -603a70a77ed18db9da4f8c7911a92b87fd21400c --WqmiTr6ek +c306add52c0752f0b3099eb669364fc4bdb74be1 +P4SrrlosMp diff --git a/examples/stateful-goroutines/stateful-goroutines.sh b/examples/stateful-goroutines/stateful-goroutines.sh index 988c051..dc26609 100644 --- a/examples/stateful-goroutines/stateful-goroutines.sh +++ b/examples/stateful-goroutines/stateful-goroutines.sh @@ -1,8 +1,9 @@ # Running our program shows that the goroutine-based -# state management example achieves about 800,000 -# operations per second. +# state management example completes about 80,000 +# total operations. $ go run stateful-goroutines.go -ops: 807434 +readOps: 71708 +writeOps: 7177 # For this particular case the goroutine-based approach # was a bit more involved than the mutex-based one. It diff --git a/public/atomic-counters b/public/atomic-counters index cfd407e..fb7e27e 100644 --- a/public/atomic-counters +++ b/public/atomic-counters @@ -44,7 +44,7 @@ counters accessed by multiple goroutines.
package main
import "fmt"
import "time"
import "sync/atomic"
-import "runtime"
ops
counter with the
Allow other goroutines to proceed.
+Wait a bit between increments.
runtime.Gosched()
+ time.Sleep(time.Millisecond)
}
}()
}
@@ -187,7 +186,7 @@ fetch the value.
$ go run atomic-counters.go
-ops: 40200
+ops: 41419
diff --git a/public/mutexes b/public/mutexes
index f145c31..4867e83 100644
--- a/public/mutexes
+++ b/public/mutexes
@@ -42,7 +42,7 @@ to safely access data across multiple goroutines.
package main
import (
"fmt"
"math/rand"
- "runtime"
"sync"
"sync/atomic"
"time"
@@ -108,14 +107,14 @@ to safely access data across multiple goroutines.
- To compare the mutex-based approach with another
-we’ll see later, ops
will count how many
-operations we perform against the state.
+ We’ll keep track of how many read and write
+operations we do.
- var ops int64 = 0
+ var readOps uint64 = 0
+ var writeOps uint64 = 0
@@ -124,7 +123,8 @@ operations we perform against the state.
Here we start 100 goroutines to execute repeated
-reads against the state.
+reads against the state, once per millisecond in
+each goroutine.
@@ -145,7 +145,7 @@ reads against the state.
exclusive access to the state
, read
the value at the chosen key,
Unlock()
the mutex, and increment
-the ops
count.
+the readOps
count.
@@ -154,7 +154,7 @@ the ops
count.
mutex.Lock()
total += state[key]
mutex.Unlock()
- atomic.AddInt64(&ops, 1)
+ atomic.AddUint64(&readOps, 1)
@@ -162,19 +162,12 @@ the ops
count.
- In order to ensure that this goroutine
-doesn’t starve the scheduler, we explicitly
-yield after each operation with
-runtime.Gosched()
. This yielding is
-handled automatically with e.g. every
-channel operation and for blocking
-calls like time.Sleep
, but in this
-case we need to do it manually.
+ Wait a bit between reads.
- runtime.Gosched()
+ time.Sleep(time.Millisecond)
}
}()
}
@@ -199,8 +192,8 @@ using the same pattern we did for reads.
mutex.Lock()
state[key] = val
mutex.Unlock()
- atomic.AddInt64(&ops, 1)
- runtime.Gosched()
+ atomic.AddUint64(&writeOps, 1)
+ time.Sleep(time.Millisecond)
}
}()
}
@@ -225,13 +218,15 @@ using the same pattern we did for reads.
- Take and report a final operations count.
+ Take and report final operation counts.
- opsFinal := atomic.LoadInt64(&ops)
- fmt.Println("ops:", opsFinal)
+ readOpsFinal := atomic.LoadUint64(&readOps)
+ fmt.Println("readOps:", readOpsFinal)
+ writeOpsFinal := atomic.LoadUint64(&writeOps)
+ fmt.Println("writeOps:", writeOpsFinal)
@@ -260,15 +255,16 @@ using the same pattern we did for reads.
Running the program shows that we executed about
-3,500,000 operations against our mutex
-synchronized
+90,000 total operations against our mutex
-synchronized
state
.
$ go run mutexes.go
-ops: 3598302
-state: map[1:38 4:98 2:23 3:85 0:44]
+readOps: 83285
+writeOps: 8320
+state: map[1:97 4:53 0:33 2:15 3:2]
diff --git a/public/stateful-goroutines b/public/stateful-goroutines
index 6f6523f..fdf3e10 100644
--- a/public/stateful-goroutines
+++ b/public/stateful-goroutines
@@ -46,7 +46,7 @@ by exactly 1 goroutine.
-
+
package main
@@ -117,7 +117,8 @@ goroutine to respond.
- var ops int64 = 0
+ var readOps uint64 = 0
+ var writeOps uint64 = 0
@@ -190,7 +191,8 @@ result over the provided resp
channel.
resp: make(chan int)}
reads <- read
<-read.resp
- atomic.AddInt64(&ops, 1)
+ atomic.AddUint64(&readOps, 1)
+ time.Sleep(time.Millisecond)
}
}()
}
@@ -216,7 +218,8 @@ approach.
resp: make(chan bool)}
writes <- write
<-write.resp
- atomic.AddInt64(&ops, 1)
+ atomic.AddUint64(&writeOps, 1)
+ time.Sleep(time.Millisecond)
}
}()
}
@@ -240,13 +243,15 @@ approach.
- Finally, capture and report the ops
count.
+ Finally, capture and report the op counts.
- opsFinal := atomic.LoadInt64(&ops)
- fmt.Println("ops:", opsFinal)
+ readOpsFinal := atomic.LoadUint64(&readOps)
+ fmt.Println("readOps:", readOpsFinal)
+ writeOpsFinal := atomic.LoadUint64(&writeOps)
+ fmt.Println("writeOps:", writeOpsFinal)
}
@@ -260,14 +265,15 @@ approach.
Running our program shows that the goroutine-based
-state management example achieves about 800,000
-operations per second.
+state management example completes about 80,000
+total operations.
$ go run stateful-goroutines.go
-ops: 807434
+readOps: 71708
+writeOps: 7177