<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 "ok" result and return on <code>!ok</code>. Great, clean pattern.</p>
<p>I have a case where I'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'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 "ok to send" channels that generators read from before generating or quit if closed, I can't see how to avoid this waste.</p>
<p>TLDR: lots of available patterns to deal with lots of consumer goroutines but I'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 "fmt"
func produce(out chan<- int, exit chan struct{}) {
for i := 0; ; i++ {
select {
case out <- i:
case <-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<- int
exit chan struct{}
}
func NewProducer(out chan<- int) *Producer {
return &Producer{
out: out,
exit: make(chan struct{}),
}
}
func (p *Producer) Start() {
go func() {
for i := 0; ; i++ {
select {
case p.out <- i:
case <-p.exit:
return
}
}
}()
}
func (p *Producer) Stop() {
close(p.exit)
}
</code></pre>
<p>That'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't know you could select across sends!</p></pre>db____db: <pre><p>A lot of people don'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 "wraps channel ops in function/method calls") in the middle. So yeah, you make "ok to send" 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 "manager" that encloses the channel and instructs producers when it's safe to send and when it's not? It's certainly more DRY than making each producer check the same "ok" channel, at least: thanks.</p>
<p>I do feel this is a rough edge in Go, that it'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 "raw primitive". For example, if you do:</p>
<pre><code>foo := <- mychan
</code></pre>
<p>Then if mychan is closed, it'll panic. Whereas:</p>
<pre><code>foo, ok := <- mychan
</code></pre>
<p>Will instead return a nil type for foo and a boolean false for ok, avoiding the panic. There's no parallel operation for sending, which is a pity. For example, it'd be nice if producers could simply:</p>
<pre><code>ok := mychan <- 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'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 <- 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't panic, but it returns the zero value for the channel'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's why (but I'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'd just gone on outdated docs or erroneous blogposts, there..</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传