From ef8f0e3831c3b888c390e0734fde4c31ee8e386d Mon Sep 17 00:00:00 2001
From: Eli Bendersky
Date: Thu, 5 Sep 2019 10:50:09 -0700
Subject: [PATCH 1/2] Deterministic example for atomics.
Fixes #265
---
examples/atomic-counters/atomic-counters.go | 39 +++++-----
examples/atomic-counters/atomic-counters.hash | 4 +-
examples/atomic-counters/atomic-counters.sh | 10 ++-
public/atomic-counters | 77 ++++++++++---------
4 files changed, 66 insertions(+), 64 deletions(-)
diff --git a/examples/atomic-counters/atomic-counters.go b/examples/atomic-counters/atomic-counters.go
index 9df3276..2a0901b 100644
--- a/examples/atomic-counters/atomic-counters.go
+++ b/examples/atomic-counters/atomic-counters.go
@@ -7,9 +7,11 @@
package main
-import "fmt"
-import "time"
-import "sync/atomic"
+import (
+ "fmt"
+ "sync"
+ "sync/atomic"
+)
func main() {
@@ -17,33 +19,28 @@ func main() {
// (always-positive) counter.
var ops uint64
- // To simulate concurrent updates, we'll start 50
- // goroutines that each increment the counter about
- // once a millisecond.
+ // A WaitGroup will help us wait for all goroutines
+ // to finish their work.
+ var wg sync.WaitGroup
+
+ // We'll start 50 goroutines that each increment the
+ // counter exactly 1000 times.
for i := 0; i < 50; i++ {
+ wg.Add(1)
+
go func() {
- for {
+ for c := 0; c < 1000; c++ {
// To atomically increment the counter we
// use `AddUint64`, giving it the memory
// address of our `ops` counter with the
// `&` syntax.
atomic.AddUint64(&ops, 1)
-
- // Wait a bit between increments.
- time.Sleep(time.Millisecond)
}
+ wg.Done()
}()
}
- // Wait a second to allow some ops to accumulate.
- time.Sleep(time.Second)
-
- // In order to safely use the counter while it's still
- // being updated by other goroutines, we extract a
- // copy of the current value into `opsFinal` via
- // `LoadUint64`. As above we need to give this
- // function the memory address `&ops` from which to
- // fetch the value.
- opsFinal := atomic.LoadUint64(&ops)
- fmt.Println("ops:", opsFinal)
+ // Wait until all the goroutines are done.
+ wg.Wait()
+ fmt.Println("ops:", ops)
}
diff --git a/examples/atomic-counters/atomic-counters.hash b/examples/atomic-counters/atomic-counters.hash
index c1b531a..e35f8f2 100644
--- a/examples/atomic-counters/atomic-counters.hash
+++ b/examples/atomic-counters/atomic-counters.hash
@@ -1,2 +1,2 @@
-a4190094ea0405b5f2733101beb97939a1d43aee
-KDr9EMMPMgi
+103c9b7d036e3a5c14dc481755b78b10dc9f894e
+GRkVf6J1--B
diff --git a/examples/atomic-counters/atomic-counters.sh b/examples/atomic-counters/atomic-counters.sh
index e4523f9..1680a10 100644
--- a/examples/atomic-counters/atomic-counters.sh
+++ b/examples/atomic-counters/atomic-counters.sh
@@ -1,7 +1,11 @@
-# Running the program shows that we executed about
-# 40,000 operations.
+# We expect to get exactly 50,000 operations. Had we
+# used the non-atomic `ops++` to increment the counter,
+# we'd likely get a different number, changing between
+# runs, because the goroutines would interfere with
+# each other. Moreover, we'd get data race failures
+# when running with the `-race` flag.
$ go run atomic-counters.go
-ops: 41419
+ops: 50000
# Next we'll look at mutexes, another tool for managing
# state.
diff --git a/public/atomic-counters b/public/atomic-counters
index b2e006e..6c9d94c 100644
--- a/public/atomic-counters
+++ b/public/atomic-counters
@@ -46,7 +46,7 @@ counters accessed by multiple goroutines.
- 
+ 
@@ -59,9 +59,11 @@ counters accessed by multiple goroutines.
|
- import "fmt"
-import "time"
-import "sync/atomic"
+ import (
+ "fmt"
+ "sync"
+ "sync/atomic"
+)
|
@@ -95,16 +97,28 @@ counters accessed by multiple goroutines.
- To simulate concurrent updates, we’ll start 50
-goroutines that each increment the counter about
-once a millisecond.
+ A WaitGroup will help us wait for all goroutines
+to finish their work.
+
+ |
+
+
+
+
+ |
+
+
+
+
+ We’ll start 50 goroutines that each increment the
+counter exactly 1000 times.
|
for i := 0; i < 50; i++ {
- go func() {
- for {
+ wg.Add(1)
|
@@ -120,7 +134,8 @@ address of our ops
counter with the
- atomic.AddUint64(&ops, 1)
+ go func() {
+ for c := 0; c < 1000; c++ {
|
@@ -128,13 +143,13 @@ address of our ops
counter with the
- Wait a bit between increments.
-
+
|
- time.Sleep(time.Millisecond)
+ atomic.AddUint64(&ops, 1)
}
+ wg.Done()
}()
}
@@ -144,31 +159,13 @@ address of our ops counter with the
- Wait a second to allow some ops to accumulate.
-
- |
-
-
- time.Sleep(time.Second)
-
-
- |
-
-
-
-
- In order to safely use the counter while it’s still
-being updated by other goroutines, we extract a
-copy of the current value into opsFinal via
-LoadUint64 . As above we need to give this
-function the memory address &ops from which to
-fetch the value.
+ Wait until all the goroutines are done.
|
- opsFinal := atomic.LoadUint64(&ops)
- fmt.Println("ops:", opsFinal)
+ wg.Wait()
+ fmt.Println("ops:", ops)
}
@@ -181,14 +178,18 @@ fetch the value.
- Running the program shows that we executed about
-40,000 operations.
+ We expect to get exactly 50,000 operations. Had we
+used the non-atomic ops++ to increment the counter,
+we’d likely get a different number, changing between
+runs, because the goroutines would interfere with
+each other. Moreover, we’d get data race failures
+when running with the -race flag.
|
$ go run atomic-counters.go
-ops: 41419
+ops: 50000
|
@@ -219,7 +220,7 @@ state.
| |