我是Go的新手,所以如果我的问题的答案显而易见,我会提前道歉:)
我计划一个生产者来读取文件并将每一行发送到一个频道,例如:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
processingChan <- scanner.Text()
}
并添加一些goroutine来消耗这些行。
现在,我想要的是,如果任何行无法在goroutine中处理(让我们说这行包含我的业务规则的无效值),我想停止生产者循环,关闭文件(已经推迟了并完成了该计划。
问题是:我怎样才能通知&#34;生产者循环/停止?
我发现有人建议:
for scanner.Scan() {
select {
case <- quit:
// break / return
default:
// send next line to channel
}
}
并且消费者goroutines将写入&#34; quit&#34;发生故障时(或错误)通道。
这种方法可能解决了这个问题,但我想知道是否有更清洁/更好或只是普通/流行的方法。
答案 0 :(得分:3)
更正,使用退出频道。特别是当你已经在循环中发送到频道时,处理额外的频道很容易。但是,我不会使用您提出的表单,而是更简单且更安全版本:
for scanner.Scan() {
select {
case <- quit:
return
case processingChan <- scanner.Text():
}
}
为什么它更安全?因为它没有死锁,与default
的示例相反。你可能很幸运,从来没有遇到它,但有些情况你会。问题在于你有两个相互交流的例程,总是需要更多关注。考虑一下:
quit := make(chan error, 1)
prod := make(chan int)
go func() {
for n := range prod {
runtime.Gosched()
if n%66 == 0 {
quit <- errors.New("2/3 of evil")
return
}
}
}()
for n := 1; n < 1000; n++ {
select {
case <-quit:
fmt.Println(n)
return
default:
prod <- n
}
}
// https://play.golang.org/p/3kDRAAwaKR
轰!主要例程是尝试发送到prod
频道,但没有人接收它;与我们的消费者一样的问题。
向频道添加缓冲区也不能解决问题,但会减少它的可能性。
将前面的示例与以下更改进行比较:
select {
case <-quit:
fmt.Println(n)
return
case prod <- n:
}
// https://play.golang.org/p/pz8DMYdrVV
很好地工作。
我理解一个人想要使用第一个选项以确保它们尽早退出,但如果您在退出之前发送一个或两个额外的项目进行处理,这通常不是一个大问题。
答案 1 :(得分:0)
我想你回答了自己的问题。在我看来,这是最干净最惯用的方法。而且我认为大多数Gophers会同意。其他选项是通过一些你必须包装在Mutex
中的变量来共享状态,你仍然有一个类似的for循环虽然它会在顶部或底部有一个if来检查标志看它是否应该中止。
我认为在创建使用者和生成器结构时,最好的设计是清楚地命名要在ReadFilesAsync
之类的Go例程中运行的方法,并在同一结构上定义退出和/或中止通道。它为您的课程的消费者提供了一个干净,简单,一致的交互方式。如果方法是异步的,则在go例程中调用它。如果要停止它,您调用方法的对象也会公开并中止通道,您可以发出信号来执行此操作。这消除了对锅炉板代码的需要,例如在调用范围中声明中止通道并将其传递给异步方法。
编辑:请注意,这更多是关于停止goroutine而不是停止for。这只是放入break
或return
的问题。如果for循环在goroutine中运行,则仅存在协调需求。
答案 2 :(得分:0)
我不觉得自己很专业,但我觉得你的方法很好。我可以想象与goroutine有点不同的方法。 goroutines的输入将是符文的通道。比要求停止可能是关闭输入通道,结果可能是每次迭代的结果。
但是在这个简单的情况下它可能比你的代码更长更慢,因此我写了一个很好的代码。