From 6a58750728a14e7bad596a213059d34e2418f90b Mon Sep 17 00:00:00 2001
From: Mark McGranaghan
Date: Tue, 27 Dec 2016 08:56:49 -0800
Subject: [PATCH] Consistently use Sleep in state-management examples
Consistently use `time.Sleep`, instead of `runtime.Gosched`, to ensure all
goroutines can make progress. `Gosched` wasn't working in the playground
(ref #149).
Also stop trying to compare operation rates. This was tenuous given e.g. how
short the programs ran for, and with the `Sleep`s we now expect the rates to
be similar anyways.
---
examples/atomic-counters/atomic-counters.go | 5 +-
examples/atomic-counters/atomic-counters.hash | 4 +-
examples/atomic-counters/atomic-counters.sh | 2 +-
examples/mutexes/mutexes.go | 39 +++++++--------
examples/mutexes/mutexes.hash | 4 +-
examples/mutexes/mutexes.sh | 7 +--
.../stateful-goroutines.go | 17 ++++---
.../stateful-goroutines.hash | 4 +-
.../stateful-goroutines.sh | 7 +--
public/atomic-counters | 9 ++--
public/mutexes | 48 +++++++++----------
public/stateful-goroutines | 26 ++++++----
12 files changed, 87 insertions(+), 85 deletions(-)
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.
-
+
@@ -60,7 +60,6 @@ counters accessed by multiple goroutines.
import "fmt"
import "time"
import "sync/atomic"
-import "runtime"
|
@@ -127,12 +126,12 @@ address of our 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.
|
-
+
@@ -58,7 +58,6 @@ to safely access data across multiple goroutines.
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.
-
+
@@ -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
|
| | |