Share memory by communicating - putting it into practice

xuanbao · · 34 次点击    
<p>I&#39;m sure most of us have heard this mantra over and over - yet I still typically find myself taking the lazy/comfortable path (e.g. sync.Mutex) whenever I have to deal with concurrent data access. </p> <p>I figured I&#39;d finally take some time to try out a solution using channels &amp; first class functions. I wrote a little cache library, and I&#39;m really happy with the results. I&#39;d be happy to hear feedback/criticisms. You can check it out at:</p> <p><a href="https://github.com/zpatrick/go-cache">https://github.com/zpatrick/go-cache</a> </p> <p>I was heavily influenced by Dave Cheney&#39;s article, which I highly recommend reading if you haven&#39;t already:</p> <p><a href="https://dave.cheney.net/2016/11/13/do-not-fear-first-class-functions">https://dave.cheney.net/2016/11/13/do-not-fear-first-class-functions</a></p> <hr/>**评论:**<br/><br/>earthboundkid: <pre><p>I like this blog post I wrote: <a href="https://blog.carlmjohnson.net/post/share-memory-by-communicating/">https://blog.carlmjohnson.net/post/share-memory-by-communicating/</a></p> <p>To boil it down, try to keep your variables local to one goroutine where practical. Don&#39;t be afraid to have a single &#34;dispatcher&#34; routine that handles coordinating work among a number of workers that are essentially synchronous. The async comes from the coordination, not the workers themselves. </p></pre>earthboundkid: <pre><p>Here&#39;s the beginning of how I would write an expiring cache: <a href="https://play.golang.org/p/-2ArLCGvW4">https://play.golang.org/p/-2ArLCGvW4</a></p> <p>There&#39;s a bit of hand waving in there, but it does compile and hopefully you can see how I mean for it to work.</p> <p>Basically, if there&#39;s one big for loop with a select statement in it that controls everything concurrent, that makes it really easy to reason about who is doing what when and prevent race conditions etc.</p></pre>zpatrick319: <pre><p>Nice! This was my first approach as well. </p> <p>That being said, the <a href="https://dave.cheney.net/2016/11/13/do-not-fear-first-class-functions" rel="nofollow">https://dave.cheney.net/2016/11/13/do-not-fear-first-class-functions</a> article actually goes into how this pattern can be improved by using a single channel that takes a first class function. The &#34;one big for loop with a select statement in it that controls everything concurrent&#34; is exactly the same, but you get the bonus of only needing a single channel to implement any behavior. </p> <p>As the example in the article shows, this:</p> <pre><code>func (m *Mux) Add(conn net.Conn) { m.add &lt;- conn } func (m *Mux) Remove(addr net.Addr) { m.remove &lt;- addr } func (m *Mux) loop() { conns := make(map[net.Addr]net.Conn) for { select { case conn := &lt;-m.add: m.conns[conn.RemoteAddr()] = conn case addr := &lt;-m.remove: delete(m.conns, addr) } } </code></pre> <p>can become this:</p> <pre><code>func (m *Mux) Add(conn net.Conn) { m.ops &lt;- func(m map[net.Addr]net.Conn) { m[conn.RemoteAddr()] = conn } } func (m *Mux) Remove(addr net.Addr) { m.ops &lt;- func(m map[net.Addr]net.Conn) { delete(m, addr) } } func (m *Mux) loop() {
 conns := make(map[net.Addr]net.Conn) for op := range m.ops { op(conns) } } </code></pre> <p>As you add more functionality, the loop() function will never need to change. </p></pre>earthboundkid: <pre><p>Hmm, it looks cool, but I&#39;m not convinced it&#39;s better than a mutex. :-) What does the loop of closures buy you in terms of code clarity that you couldn&#39;t get by just grabbing a lock?</p></pre>mcouturier: <pre><p>For one you follow the open-closed principle: the system is open to extensions, but closed to modifications (loop func).</p></pre>mcouturier: <pre><p>I&#39;m just wondering how you cleanup that thing (shutdown function)</p></pre>zpatrick319: <pre><p>It&#39;s not exposed in my example or the article, but running close() on a channel will stop the range loop: <a href="https://gobyexample.com/range-over-channels" rel="nofollow">https://gobyexample.com/range-over-channels</a></p></pre>mcouturier: <pre><p>Yes but sending on a closed channel will also panics. I guess you have to state that any call to the methods after a close will do.</p></pre>teepark: <pre><p>Your Items method should make a copy and send that back over the chan. As it is the caller could mutate the (shared) map after getting it.</p></pre>zpatrick319: <pre><p>good eye - thanks man </p></pre>alasijia: <pre><p>&#34;Share memory by communicating&#34; is not a good description for the functionalities of channels. &#34;Transfer value ownership&#34; is a better one.</p></pre>Femaref: <pre><p>it&#39;s a quote from <a href="https://golang.org/doc/effective_go.html#sharing" rel="nofollow">effective go</a>. What they describe there is what you are describing as well.</p></pre>
34 次点击  
加入收藏 微博
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet