Clean Pattern for Stopping Generator Goroutines?

xuanbao · · 547 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>If I want to stop consumer goroutines from reading a channel amd to clean up and exit, I can just close the channel and code the goroutine to pull an &#34;ok&#34; result and return on <code>!ok</code>. Great, clean pattern.</p> <p>I have a case where I&#39;d like to spin up loads of generator goroutines that iterate through loads of permutations of an input, to be consumed by a far smaller number of processing consumer goroutines. At some point, a generator will return a value that satisfies the output, and any firther permutation is pointless waste.</p> <p>Problem is, when sending on a channel you can&#39;t check for openness, and failure to send triggers a panic rather than allowing you to pull an <code>ok</code> result. So without creating a horribly convoluted system involving &#34;ok to send&#34; channels that generators read from before generating or quit if closed, I can&#39;t see how to avoid this waste.</p> <p>TLDR: lots of available patterns to deal with lots of consumer goroutines but I&#39;m looking for a pattern for dealing with lots of producer goroutines, where the required output is impossible to predict and once found obviates the need for all further production?</p> <hr/>**评论:**<br/><br/>calebdoxsey: <pre><p>Its the same pattern for production:</p> <pre><code>package main import &#34;fmt&#34; func produce(out chan&lt;- int, exit chan struct{}) { for i := 0; ; i++ { select { case out &lt;- i: case &lt;-exit: return } } } func main() { ints := make(chan int) exit := make(chan struct{}) go produce(ints, exit) defer close(exit) for i := range ints { fmt.Println(i) if i == 143 { break } } } </code></pre> <p>You could wrap the producer / consumer in a struct with <code>Start</code> and <code>Stop</code> (or <code>Close</code>) methods and have a per-producer exit channel:</p> <pre><code>type Producer struct { out chan&lt;- int exit chan struct{} } func NewProducer(out chan&lt;- int) *Producer { return &amp;Producer{ out: out, exit: make(chan struct{}), } } func (p *Producer) Start() { go func() { for i := 0; ; i++ { select { case p.out &lt;- i: case &lt;-p.exit: return } } }() } func (p *Producer) Stop() { close(p.exit) } </code></pre> <p>That&#39;s a little easier to work with and you can pull out an interface which every producer and consumer implements.</p></pre>cathalgarvey: <pre><p>Awesome! I didn&#39;t know you could select across sends!</p></pre>db____db: <pre><p>A lot of people don&#39;t. I was one of them until fairly recently.</p></pre>tv64738: <pre><p><a href="http://blog.golang.org/pipelines">http://blog.golang.org/pipelines</a></p></pre>tcardv: <pre><p>When communication between too concurrent processes gets to this level I would put a functional API (as in &#34;wraps channel ops in function/method calls&#34;) in the middle. So yeah, you make &#34;ok to send&#34; channels, but make it nice to communicate.</p> <p>For example: <a href="http://play.golang.org/p/1PX6Bu2Dyx" rel="nofollow">http://play.golang.org/p/1PX6Bu2Dyx</a> (That particular API may or may not be appropriate for your case.)</p></pre>cathalgarvey: <pre><p>Hm.. so that pattern suggests creating a self-enclosed &#34;manager&#34; that encloses the channel and instructs producers when it&#39;s safe to send and when it&#39;s not? It&#39;s certainly more DRY than making each producer check the same &#34;ok&#34; channel, at least: thanks.</p> <p>I do feel this is a rough edge in Go, that it&#39;s so easy to check if a chan is closed by reading from it but not by sending to it. :-/</p></pre>tcardv: <pre><p>Channels are primitives. They are supposed to be minimalist. You have tools to combine primitives and then abstract over the combinations.</p></pre>cathalgarvey: <pre><p>I was referring to the little features that Go offers for receiving that go beyond the &#34;raw primitive&#34;. For example, if you do:</p> <pre><code>foo := &lt;- mychan </code></pre> <p>Then if mychan is closed, it&#39;ll panic. Whereas:</p> <pre><code>foo, ok := &lt;- mychan </code></pre> <p>Will instead return a nil type for foo and a boolean false for ok, avoiding the panic. There&#39;s no parallel operation for sending, which is a pity. For example, it&#39;d be nice if producers could simply:</p> <pre><code>ok := mychan &lt;- some_value </code></pre> <p>..and have the statement return <code>false</code> if mychan is closed, or true if it sends successfully. While the producer select statement (which I didn&#39;t know about and is the next-best-thing) is nice, having the send statement return a result <em>if asked</em> in the same way as the receive statement would allow a much, much cleaner producer pattern:</p> <pre><code>go func(recv chan string) { for { some_string := generate_unique_string(time.Now()) ok := recv &lt;- some_string if !ok { // recv channel closed, end production cycle return } } } </code></pre></pre>tcardv: <pre><p>Small correction: if you read from a closed channel, it doesn&#39;t panic, but it returns the zero value for the channel&#39;s element type. (See <a href="http://play.golang.org/p/SVyIRZhQkp" rel="nofollow">http://play.golang.org/p/SVyIRZhQkp</a> )</p> <p>There used to be a <code>closed</code> builtin function. <a href="https://github.com/golang/go/commit/3f915f51a8d16e97ac631dfb4dd5946ca675a2b8" rel="nofollow">It was removed.</a> The sender <a href="https://godoc.org/builtin#close" rel="nofollow">should be the one closing the channel</a>, so maybe that&#39;s why (but I&#39;m not sure, since a channel can have multiple senders).</p> <p>And yeah, the select thing is clearer. I still would prefer to put a functional interface on top of it most of the time.</p></pre>cathalgarvey: <pre><p>Ah, apologies I thought behaviour differed based on whether an <code>ok</code> is called for. I avoid using the panic/recover system as I personally hate it, so I&#39;d just gone on outdated docs or erroneous blogposts, there..</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

547 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传