<p>I'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'd finally take some time to try out a solution using channels & first class functions. I wrote a little cache library, and I'm really happy with the results. I'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's article, which I highly recommend reading if you haven'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't be afraid to have a single "dispatcher" 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'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'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'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 "one big for loop with a select statement in it that controls everything concurrent" 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 <- conn
}
func (m *Mux) Remove(addr net.Addr) {
m.remove <- addr
}
func (m *Mux) loop() {
conns := make(map[net.Addr]net.Conn)
for {
select {
case conn := <-m.add:
m.conns[conn.RemoteAddr()] = conn
case addr := <-m.remove:
delete(m.conns, addr)
}
}
</code></pre>
<p>can become this:</p>
<pre><code>func (m *Mux) Add(conn net.Conn) {
m.ops <- func(m map[net.Addr]net.Conn) {
m[conn.RemoteAddr()] = conn
}
}
func (m *Mux) Remove(addr net.Addr) {
m.ops <- 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'm not convinced it's better than a mutex. :-) What does the loop of closures buy you in terms of code clarity that you couldn'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'm just wondering how you cleanup that thing (shutdown function)</p></pre>zpatrick319: <pre><p>It'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>"Share memory by communicating" is not a good description for the functionalities of channels. "Transfer value ownership" is a better one.</p></pre>Femaref: <pre><p>it'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>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传