gobyexample/public/stateful-goroutines
Mark McGranaghan 78e1fd61ec Clean up merge
2019-09-01 16:09:36 -07:00

320 lines
17 KiB
Plaintext
Generated

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Go by Example: Stateful Goroutines</title>
<link rel=stylesheet href="site.css">
</head>
<script>
onkeydown = (e) => {
if (e.key == "ArrowLeft") {
window.location.href = 'mutexes';
}
if (e.key == "ArrowRight") {
window.location.href = 'sorting';
}
}
</script>
<body>
<div class="example" id="stateful-goroutines">
<h2><a href="./">Go by Example</a>: Stateful Goroutines</h2>
<table>
<tr>
<td class="docs">
<p>In the previous example we used explicit locking with
<a href="mutexes">mutexes</a> to synchronize access to shared state
across multiple goroutines. Another option is to use the
built-in synchronization features of goroutines and
channels to achieve the same result. This channel-based
approach aligns with Go&rsquo;s ideas of sharing memory by
communicating and having each piece of data owned
by exactly 1 goroutine.</p>
</td>
<td class="code empty leading">
</td>
</tr>
<tr>
<td class="docs">
</td>
<td class="code leading">
<a href="http://play.golang.org/p/saQTLpdIgp2"><img title="Run code" src="play.png" class="run" /></a><img title="Copy code" src="clipboard.png" class="copy" />
<div class="highlight"><pre><span class="kn">package</span> <span class="nx">main</span>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
</td>
<td class="code leading">
<div class="highlight"><pre><span class="kn">import</span> <span class="p">(</span>
<span class="s">&quot;fmt&quot;</span>
<span class="s">&quot;math/rand&quot;</span>
<span class="s">&quot;sync/atomic&quot;</span>
<span class="s">&quot;time&quot;</span>
<span class="p">)</span>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
<p>In this example our state will be owned by a single
goroutine. This will guarantee that the data is never
corrupted with concurrent access. In order to read or
write that state, other goroutines will send messages
to the owning goroutine and receive corresponding
replies. These <code>readOp</code> and <code>writeOp</code> <code>struct</code>s
encapsulate those requests and a way for the owning
goroutine to respond.</p>
</td>
<td class="code leading">
<div class="highlight"><pre><span class="kd">type</span> <span class="nx">readOp</span> <span class="kd">struct</span> <span class="p">{</span>
<span class="nx">key</span> <span class="kt">int</span>
<span class="nx">resp</span> <span class="kd">chan</span> <span class="kt">int</span>
<span class="p">}</span>
<span class="kd">type</span> <span class="nx">writeOp</span> <span class="kd">struct</span> <span class="p">{</span>
<span class="nx">key</span> <span class="kt">int</span>
<span class="nx">val</span> <span class="kt">int</span>
<span class="nx">resp</span> <span class="kd">chan</span> <span class="kt">bool</span>
<span class="p">}</span>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
</td>
<td class="code leading">
<div class="highlight"><pre><span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
<p>As before we&rsquo;ll count how many operations we perform.</p>
</td>
<td class="code leading">
<div class="highlight"><pre> <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></div>
</td>
</tr>
<tr>
<td class="docs">
<p>The <code>reads</code> and <code>writes</code> channels will be used by
other goroutines to issue read and write requests,
respectively.</p>
</td>
<td class="code leading">
<div class="highlight"><pre> <span class="nx">reads</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">readOp</span><span class="p">)</span>
<span class="nx">writes</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="nx">writeOp</span><span class="p">)</span>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
<p>Here is the goroutine that owns the <code>state</code>, which
is a map as in the previous example but now private
to the stateful goroutine. This goroutine repeatedly
selects on the <code>reads</code> and <code>writes</code> channels,
responding to requests as they arrive. A response
is executed by first performing the requested
operation and then sending a value on the response
channel <code>resp</code> to indicate success (and the desired
value in the case of <code>reads</code>).</p>
</td>
<td class="code leading">
<div class="highlight"><pre> <span class="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
<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>
<span class="k">for</span> <span class="p">{</span>
<span class="k">select</span> <span class="p">{</span>
<span class="k">case</span> <span class="nx">read</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="nx">reads</span><span class="p">:</span>
<span class="nx">read</span><span class="p">.</span><span class="nx">resp</span> <span class="o">&lt;-</span> <span class="nx">state</span><span class="p">[</span><span class="nx">read</span><span class="p">.</span><span class="nx">key</span><span class="p">]</span>
<span class="k">case</span> <span class="nx">write</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="nx">writes</span><span class="p">:</span>
<span class="nx">state</span><span class="p">[</span><span class="nx">write</span><span class="p">.</span><span class="nx">key</span><span class="p">]</span> <span class="p">=</span> <span class="nx">write</span><span class="p">.</span><span class="nx">val</span>
<span class="nx">write</span><span class="p">.</span><span class="nx">resp</span> <span class="o">&lt;-</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}()</span>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
<p>This starts 100 goroutines to issue reads to the
state-owning goroutine via the <code>reads</code> channel.
Each read requires constructing a <code>readOp</code>, sending
it over the <code>reads</code> channel, and the receiving the
result over the provided <code>resp</code> channel.</p>
</td>
<td class="code leading">
<div class="highlight"><pre> <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="k">for</span> <span class="p">{</span>
<span class="nx">read</span> <span class="o">:=</span> <span class="nx">readOp</span><span class="p">{</span>
<span class="nx">key</span><span class="p">:</span> <span class="nx">rand</span><span class="p">.</span><span class="nx">Intn</span><span class="p">(</span><span class="mi">5</span><span class="p">),</span>
<span class="nx">resp</span><span class="p">:</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="kt">int</span><span class="p">)}</span>
<span class="nx">reads</span> <span class="o">&lt;-</span> <span class="nx">read</span>
<span class="o">&lt;-</span><span class="nx">read</span><span class="p">.</span><span class="nx">resp</span>
<span class="nx">atomic</span><span class="p">.</span><span class="nx">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>
<span class="nx">time</span><span class="p">.</span><span class="nx">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>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
<p>We start 10 writes as well, using a similar
approach.</p>
</td>
<td class="code leading">
<div class="highlight"><pre> <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="k">go</span> <span class="kd">func</span><span class="p">()</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">{</span>
<span class="nx">write</span> <span class="o">:=</span> <span class="nx">writeOp</span><span class="p">{</span>
<span class="nx">key</span><span class="p">:</span> <span class="nx">rand</span><span class="p">.</span><span class="nx">Intn</span><span class="p">(</span><span class="mi">5</span><span class="p">),</span>
<span class="nx">val</span><span class="p">:</span> <span class="nx">rand</span><span class="p">.</span><span class="nx">Intn</span><span class="p">(</span><span class="mi">100</span><span class="p">),</span>
<span class="nx">resp</span><span class="p">:</span> <span class="nb">make</span><span class="p">(</span><span class="kd">chan</span> <span class="kt">bool</span><span class="p">)}</span>
<span class="nx">writes</span> <span class="o">&lt;-</span> <span class="nx">write</span>
<span class="o">&lt;-</span><span class="nx">write</span><span class="p">.</span><span class="nx">resp</span>
<span class="nx">atomic</span><span class="p">.</span><span class="nx">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="nx">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>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
<p>Let the goroutines work for a second.</p>
</td>
<td class="code leading">
<div class="highlight"><pre> <span class="nx">time</span><span class="p">.</span><span class="nx">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
<p>Finally, capture and report the op counts.</p>
</td>
<td class="code">
<div class="highlight"><pre> <span class="nx">readOpsFinal</span> <span class="o">:=</span> <span class="nx">atomic</span><span class="p">.</span><span class="nx">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="nx">Println</span><span class="p">(</span><span class="s">&quot;readOps:&quot;</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="nx">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="nx">Println</span><span class="p">(</span><span class="s">&quot;writeOps:&quot;</span><span class="p">,</span> <span class="nx">writeOpsFinal</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>
</td>
</tr>
</table>
<table>
<tr>
<td class="docs">
<p>Running our program shows that the goroutine-based
state management example completes about 80,000
total operations.</p>
</td>
<td class="code leading">
<div class="highlight"><pre><span class="gp">$</span> go run stateful-goroutines.go
<span class="go">readOps: 71708</span>
<span class="go">writeOps: 7177</span>
</pre></div>
</td>
</tr>
<tr>
<td class="docs">
<p>For this particular case the goroutine-based approach
was a bit more involved than the mutex-based one. It
might be useful in certain cases though, for example
where you have other channels involved or when managing
multiple such mutexes would be error-prone. You should
use whichever approach feels most natural, especially
with respect to understanding the correctness of your
program.</p>
</td>
<td class="code empty">
</td>
</tr>
</table>
<p class="next">
Next example: <a href="sorting">Sorting</a>.
</p>
<p class="footer">
by <a href="https://markmcgranaghan.com">Mark McGranaghan</a> | <a href="https://github.com/mmcgrana/gobyexample/blob/master/examples/stateful-goroutines">source</a> | <a href="https://github.com/mmcgrana/gobyexample#license">license</a>
</p>
</div>
<script>
var codeLines = [];
codeLines.push('');codeLines.push('package main\u000A');codeLines.push('import (\u000A \"fmt\"\u000A \"math/rand\"\u000A \"sync/atomic\"\u000A \"time\"\u000A)\u000A');codeLines.push('type readOp struct {\u000A key int\u000A resp chan int\u000A}\u000Atype writeOp struct {\u000A key int\u000A val int\u000A resp chan bool\u000A}\u000A');codeLines.push('func main() {\u000A');codeLines.push(' var readOps uint64\u000A var writeOps uint64\u000A');codeLines.push(' reads := make(chan readOp)\u000A writes := make(chan writeOp)\u000A');codeLines.push(' go func() {\u000A var state = make(map[int]int)\u000A for {\u000A select {\u000A case read := \x3C-reads:\u000A read.resp \x3C- state[read.key]\u000A case write := \x3C-writes:\u000A state[write.key] = write.val\u000A write.resp \x3C- true\u000A }\u000A }\u000A }()\u000A');codeLines.push(' for r := 0; r \x3C 100; r++ {\u000A go func() {\u000A for {\u000A read := readOp{\u000A key: rand.Intn(5),\u000A resp: make(chan int)}\u000A reads \x3C- read\u000A \x3C-read.resp\u000A atomic.AddUint64(&readOps, 1)\u000A time.Sleep(time.Millisecond)\u000A }\u000A }()\u000A }\u000A');codeLines.push(' for w := 0; w \x3C 10; w++ {\u000A go func() {\u000A for {\u000A write := writeOp{\u000A key: rand.Intn(5),\u000A val: rand.Intn(100),\u000A resp: make(chan bool)}\u000A writes \x3C- write\u000A \x3C-write.resp\u000A atomic.AddUint64(&writeOps, 1)\u000A time.Sleep(time.Millisecond)\u000A }\u000A }()\u000A }\u000A');codeLines.push(' time.Sleep(time.Second)\u000A');codeLines.push(' readOpsFinal := atomic.LoadUint64(&readOps)\u000A fmt.Println(\"readOps:\", readOpsFinal)\u000A writeOpsFinal := atomic.LoadUint64(&writeOps)\u000A fmt.Println(\"writeOps:\", writeOpsFinal)\u000A}\u000A');codeLines.push('');codeLines.push('');
</script>
<script src="site.js" async></script>
</body>
</html>