Change mutexes example to make it simpler and more predictable

Fixes #364
This commit is contained in:
Eli Bendersky 2021-10-30 06:20:02 -07:00
parent e10011f90f
commit f09cadcbd9
4 changed files with 142 additions and 224 deletions

View File

@ -7,79 +7,57 @@ package main
import ( import (
"fmt" "fmt"
"math/rand"
"sync" "sync"
"sync/atomic"
"time"
) )
func main() { // Container holds a map of counters; since we want to
// update it concurrently from multiple goroutines, we
// For our example the `state` will be a map. // add a `Mutex` to synchronize access. The mutex is
var state = make(map[int]int) // _embedded_ in this `struct`; this is idiomatic in Go.
// Note that mutexes must not be copied, so if this
// This `mutex` will synchronize access to `state`. // `struct` is passed around, it should be done by
var mutex = &sync.Mutex{} // pointer.
type Container struct {
// We'll keep track of how many read and write sync.Mutex
// operations we do. counters map[string]int
var readOps uint64 }
var writeOps uint64
func (c *Container) inc(name string) {
// Here we start 100 goroutines to execute repeated // Lock the mutex before accessing `counters`; unlock
// reads against the state, once per millisecond in // it at the end of the function using a [defer](defer)
// each goroutine. // statement. Since the mutex is embedded into
for r := 0; r < 100; r++ { // `Container`, we can call the mutex's methods like
go func() { // `Lock` directly on `c`.
total := 0 c.Lock()
for { defer c.Unlock()
c.counters[name]++
// For each read we pick a key to access, }
// `Lock()` the `mutex` to ensure
// exclusive access to the `state`, read func main() {
// the value at the chosen key, c := Container{
// `Unlock()` the mutex, and increment counters: map[string]int{"a": 0, "b": 0},
// the `readOps` count. }
key := rand.Intn(5)
mutex.Lock() var wg sync.WaitGroup
total += state[key]
mutex.Unlock() // This function increments a named counter
atomic.AddUint64(&readOps, 1) // in a loop.
doIncrement := func(name string, n int) {
// Wait a bit between reads. for i := 0; i < n; i++ {
time.Sleep(time.Millisecond) c.inc(name)
} }
}() wg.Done()
} }
// We'll also start 10 goroutines to simulate writes, // Run several goroutines concurrently; note
// using the same pattern we did for reads. // that they all access the same `Container`,
for w := 0; w < 10; w++ { // and two of them access the same counter.
go func() { wg.Add(3)
for { go doIncrement("a", 10000)
key := rand.Intn(5) go doIncrement("a", 10000)
val := rand.Intn(100) go doIncrement("b", 10000)
mutex.Lock()
state[key] = val // Wait a for the goroutines to finish
mutex.Unlock() wg.Wait()
atomic.AddUint64(&writeOps, 1) fmt.Println(c.counters)
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()
} }

View File

@ -1,2 +1,2 @@
253b089b8145fc57a90ae4024346b6db2ec1659b 07179e54fb3466ab01ac8aa9550feb213a206785
CHCDredHCOz i50fhu4l-n0

View File

@ -1,10 +1,7 @@
# Running the program shows that we executed about # Running the program shows that the counters
# 90,000 total operations against our `mutex`-synchronized # updated as expected.
# `state`.
$ go run mutexes.go $ go run mutexes.go
readOps: 83285 map[a:20000 b:10000]
writeOps: 8320
state: map[1:97 4:53 0:33 2:15 3:2]
# Next we'll look at implementing this same state # Next we'll look at implementing this same state
# management task using only goroutines and channels. # management task using only goroutines and channels.

231
public/mutexes generated
View File

@ -44,7 +44,7 @@ to safely access data across multiple goroutines.</p>
</td> </td>
<td class="code leading"> <td class="code leading">
<a href="http://play.golang.org/p/CHCDredHCOz"><img title="Run code" src="play.png" class="run" /></a><img title="Copy code" src="clipboard.png" class="copy" /> <a href="http://play.golang.org/p/i50fhu4l-n0"><img title="Run code" src="play.png" class="run" /></a><img title="Copy code" src="clipboard.png" class="copy" />
<pre class="chroma"><span class="kn">package</span> <span class="nx">main</span> <pre class="chroma"><span class="kn">package</span> <span class="nx">main</span>
</pre> </pre>
</td> </td>
@ -58,15 +58,64 @@ to safely access data across multiple goroutines.</p>
<pre class="chroma"><span class="kn">import</span> <span class="p">(</span> <pre class="chroma"><span class="kn">import</span> <span class="p">(</span>
<span class="s">&#34;fmt&#34;</span> <span class="s">&#34;fmt&#34;</span>
<span class="s">&#34;math/rand&#34;</span>
<span class="s">&#34;sync&#34;</span> <span class="s">&#34;sync&#34;</span>
<span class="s">&#34;sync/atomic&#34;</span>
<span class="s">&#34;time&#34;</span>
<span class="p">)</span> <span class="p">)</span>
</pre> </pre>
</td> </td>
</tr> </tr>
<tr>
<td class="docs">
<p>Container holds a map of counters; since we want to
update it concurrently from multiple goroutines, we
add a <code>Mutex</code> to synchronize access. The mutex is
<em>embedded</em> in this <code>struct</code>; this is idiomatic in Go.
Note that mutexes must not be copied, so if this
<code>struct</code> is passed around, it should be done by
pointer.</p>
</td>
<td class="code leading">
<pre class="chroma">
<span class="kd">type</span> <span class="nx">Container</span> <span class="kd">struct</span> <span class="p">{</span>
<span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span>
<span class="nx">counters</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span>
<span class="p">}</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
<p>Lock the mutex before accessing <code>counters</code>; unlock
it at the end of the function using a <a href="defer">defer</a>
statement. Since the mutex is embedded into
<code>Container</code>, we can call the mutex&rsquo;s methods like
<code>Lock</code> directly on <code>c</code>.</p>
</td>
<td class="code leading">
<pre class="chroma"><span class="kd">func</span> <span class="p">(</span><span class="nx">c</span> <span class="o">*</span><span class="nx">Container</span><span class="p">)</span> <span class="nf">inc</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
</td>
<td class="code leading">
<pre class="chroma"> <span class="nx">c</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
<span class="k">defer</span> <span class="nx">c</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">counters</span><span class="p">[</span><span class="nx">name</span><span class="p">]</span><span class="o">++</span>
<span class="p">}</span>
</pre>
</td>
</tr>
<tr> <tr>
<td class="docs"> <td class="docs">
@ -74,102 +123,8 @@ to safely access data across multiple goroutines.</p>
<td class="code leading"> <td class="code leading">
<pre class="chroma"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> <pre class="chroma"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</pre> <span class="nx">c</span> <span class="o">:=</span> <span class="nx">Container</span><span class="p">{</span>
</td> <span class="nx">counters</span><span class="p">:</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">{</span><span class="s">&#34;a&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s">&#34;b&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">},</span>
</tr>
<tr>
<td class="docs">
<p>For our example the <code>state</code> will be a map.</p>
</td>
<td class="code leading">
<pre class="chroma">
<span class="kd">var</span> <span class="nx">state</span> <span class="p">=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="kt">int</span><span class="p">)</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
<p>This <code>mutex</code> will synchronize access to <code>state</code>.</p>
</td>
<td class="code leading">
<pre class="chroma">
<span class="kd">var</span> <span class="nx">mutex</span> <span class="p">=</span> <span class="o">&amp;</span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="p">{}</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
<p>We&rsquo;ll keep track of how many read and write
operations we do.</p>
</td>
<td class="code leading">
<pre class="chroma">
<span class="kd">var</span> <span class="nx">readOps</span> <span class="kt">uint64</span>
<span class="kd">var</span> <span class="nx">writeOps</span> <span class="kt">uint64</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
<p>Here we start 100 goroutines to execute repeated
reads against the state, once per millisecond in
each goroutine.</p>
</td>
<td class="code leading">
<pre class="chroma">
<span class="k">for</span> <span class="nx">r</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">r</span> <span class="p">&lt;</span> <span class="mi">100</span><span class="p">;</span> <span class="nx">r</span><span class="o">++</span> <span class="p">{</span>
<span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">total</span> <span class="o">:=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="p">{</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
<p>For each read we pick a key to access,
<code>Lock()</code> the <code>mutex</code> to ensure
exclusive access to the <code>state</code>, read
the value at the chosen key,
<code>Unlock()</code> the mutex, and increment
the <code>readOps</code> count.</p>
</td>
<td class="code leading">
<pre class="chroma">
<span class="nx">key</span> <span class="o">:=</span> <span class="nx">rand</span><span class="p">.</span><span class="nf">Intn</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="nx">mutex</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
<span class="nx">total</span> <span class="o">+=</span> <span class="nx">state</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span>
<span class="nx">mutex</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
<span class="nx">atomic</span><span class="p">.</span><span class="nf">AddUint64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">readOps</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
<p>Wait a bit between reads.</p>
</td>
<td class="code leading">
<pre class="chroma">
<span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}()</span>
<span class="p">}</span> <span class="p">}</span>
</pre> </pre>
</td> </td>
@ -177,25 +132,29 @@ the <code>readOps</code> count.</p>
<tr> <tr>
<td class="docs"> <td class="docs">
<p>We&rsquo;ll also start 10 goroutines to simulate writes,
using the same pattern we did for reads.</p> </td>
<td class="code leading">
<pre class="chroma"> <span class="kd">var</span> <span class="nx">wg</span> <span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
<p>This function increments a named counter
in a loop.</p>
</td> </td>
<td class="code leading"> <td class="code leading">
<pre class="chroma"> <pre class="chroma">
<span class="k">for</span> <span class="nx">w</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">w</span> <span class="p">&lt;</span> <span class="mi">10</span><span class="p">;</span> <span class="nx">w</span><span class="o">++</span> <span class="p">{</span> <span class="nx">doIncrement</span> <span class="o">:=</span> <span class="kd">func</span><span class="p">(</span><span class="nx">name</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">n</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
<span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span> <span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="p">&lt;</span> <span class="nx">n</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">{</span> <span class="nx">c</span><span class="p">.</span><span class="nf">inc</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span>
<span class="nx">key</span> <span class="o">:=</span> <span class="nx">rand</span><span class="p">.</span><span class="nf">Intn</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">}</span>
<span class="nx">val</span> <span class="o">:=</span> <span class="nx">rand</span><span class="p">.</span><span class="nf">Intn</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span>
<span class="nx">mutex</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span>
<span class="nx">state</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="p">=</span> <span class="nx">val</span>
<span class="nx">mutex</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
<span class="nx">atomic</span><span class="p">.</span><span class="nf">AddUint64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">writeOps</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}()</span>
<span class="p">}</span> <span class="p">}</span>
</pre> </pre>
</td> </td>
@ -203,45 +162,32 @@ using the same pattern we did for reads.</p>
<tr> <tr>
<td class="docs"> <td class="docs">
<p>Let the 10 goroutines work on the <code>state</code> and <p>Run several goroutines concurrently; note
<code>mutex</code> for a second.</p> that they all access the same <code>Container</code>,
and two of them access the same counter.</p>
</td> </td>
<td class="code leading"> <td class="code leading">
<pre class="chroma"> <pre class="chroma">
<span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span> <span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
<span class="k">go</span> <span class="nf">doIncrement</span><span class="p">(</span><span class="s">&#34;a&#34;</span><span class="p">,</span> <span class="mi">10000</span><span class="p">)</span>
<span class="k">go</span> <span class="nf">doIncrement</span><span class="p">(</span><span class="s">&#34;a&#34;</span><span class="p">,</span> <span class="mi">10000</span><span class="p">)</span>
<span class="k">go</span> <span class="nf">doIncrement</span><span class="p">(</span><span class="s">&#34;b&#34;</span><span class="p">,</span> <span class="mi">10000</span><span class="p">)</span>
</pre> </pre>
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="docs"> <td class="docs">
<p>Take and report final operation counts.</p> <p>Wait a for the goroutines to finish</p>
</td>
<td class="code leading">
<pre class="chroma">
<span class="nx">readOpsFinal</span> <span class="o">:=</span> <span class="nx">atomic</span><span class="p">.</span><span class="nf">LoadUint64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">readOps</span><span class="p">)</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;readOps:&#34;</span><span class="p">,</span> <span class="nx">readOpsFinal</span><span class="p">)</span>
<span class="nx">writeOpsFinal</span> <span class="o">:=</span> <span class="nx">atomic</span><span class="p">.</span><span class="nf">LoadUint64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">writeOps</span><span class="p">)</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;writeOps:&#34;</span><span class="p">,</span> <span class="nx">writeOpsFinal</span><span class="p">)</span>
</pre>
</td>
</tr>
<tr>
<td class="docs">
<p>With a final lock of <code>state</code>, show how it ended up.</p>
</td> </td>
<td class="code"> <td class="code">
<pre class="chroma"> <pre class="chroma">
<span class="nx">mutex</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span> <span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;state:&#34;</span><span class="p">,</span> <span class="nx">state</span><span class="p">)</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">counters</span><span class="p">)</span>
<span class="nx">mutex</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span>
<span class="p">}</span> <span class="p">}</span>
</pre> </pre>
</td> </td>
@ -253,18 +199,15 @@ using the same pattern we did for reads.</p>
<tr> <tr>
<td class="docs"> <td class="docs">
<p>Running the program shows that we executed about <p>Running the program shows that the counters
90,000 total operations against our <code>mutex</code>-synchronized updated as expected.</p>
<code>state</code>.</p>
</td> </td>
<td class="code leading"> <td class="code leading">
<pre class="chroma"> <pre class="chroma">
<span class="gp">$</span> go run mutexes.go <span class="gp">$</span> go run mutexes.go
<span class="go">readOps: 83285 <span class="go">map[a:20000 b:10000]</span></pre>
</span><span class="go">writeOps: 8320
</span><span class="go">state: map[1:97 4:53 0:33 2:15 3:2]</span></pre>
</td> </td>
</tr> </tr>
@ -295,7 +238,7 @@ management task using only goroutines and channels.</p>
</div> </div>
<script> <script>
var codeLines = []; var codeLines = [];
codeLines.push('');codeLines.push('package main\u000A');codeLines.push('import (\u000A \"fmt\"\u000A \"math/rand\"\u000A \"sync\"\u000A \"sync/atomic\"\u000A \"time\"\u000A)\u000A');codeLines.push('func main() {\u000A');codeLines.push(' var state \u003D make(map[int]int)\u000A');codeLines.push(' var mutex \u003D \u0026sync.Mutex{}\u000A');codeLines.push(' var readOps uint64\u000A var writeOps uint64\u000A');codeLines.push(' for r :\u003D 0; r \u003C 100; r++ {\u000A go func() {\u000A total :\u003D 0\u000A for {\u000A');codeLines.push(' key :\u003D rand.Intn(5)\u000A mutex.Lock()\u000A total +\u003D state[key]\u000A mutex.Unlock()\u000A atomic.AddUint64(\u0026readOps, 1)\u000A');codeLines.push(' time.Sleep(time.Millisecond)\u000A }\u000A }()\u000A }\u000A');codeLines.push(' for w :\u003D 0; w \u003C 10; w++ {\u000A go func() {\u000A for {\u000A key :\u003D rand.Intn(5)\u000A val :\u003D rand.Intn(100)\u000A mutex.Lock()\u000A state[key] \u003D val\u000A mutex.Unlock()\u000A atomic.AddUint64(\u0026writeOps, 1)\u000A time.Sleep(time.Millisecond)\u000A }\u000A }()\u000A }\u000A');codeLines.push(' time.Sleep(time.Second)\u000A');codeLines.push(' readOpsFinal :\u003D atomic.LoadUint64(\u0026readOps)\u000A fmt.Println(\"readOps:\", readOpsFinal)\u000A writeOpsFinal :\u003D atomic.LoadUint64(\u0026writeOps)\u000A fmt.Println(\"writeOps:\", writeOpsFinal)\u000A');codeLines.push(' mutex.Lock()\u000A fmt.Println(\"state:\", state)\u000A mutex.Unlock()\u000A}\u000A');codeLines.push('');codeLines.push(''); codeLines.push('');codeLines.push('package main\u000A');codeLines.push('import (\u000A \"fmt\"\u000A \"sync\"\u000A)\u000A');codeLines.push('type Container struct {\u000A sync.Mutex\u000A counters map[string]int\u000A}\u000A');codeLines.push('func (c *Container) inc(name string) {\u000A');codeLines.push(' c.Lock()\u000A defer c.Unlock()\u000A c.counters[name]++\u000A}\u000A');codeLines.push('func main() {\u000A c :\u003D Container{\u000A counters: map[string]int{\"a\": 0, \"b\": 0},\u000A }\u000A');codeLines.push(' var wg sync.WaitGroup\u000A');codeLines.push(' doIncrement :\u003D func(name string, n int) {\u000A for i :\u003D 0; i \u003C n; i++ {\u000A c.inc(name)\u000A }\u000A wg.Done()\u000A }\u000A');codeLines.push(' wg.Add(3)\u000A go doIncrement(\"a\", 10000)\u000A go doIncrement(\"a\", 10000)\u000A go doIncrement(\"b\", 10000)\u000A');codeLines.push(' wg.Wait()\u000A fmt.Println(c.counters)\u000A}\u000A');codeLines.push('');codeLines.push('');
</script> </script>
<script src="site.js" async></script> <script src="site.js" async></script>
</body> </body>