golang 惯用的方式来停止 for
golang idiomatic way to stop a for
我是 Go 的新手,所以如果我的问题的答案很明显,我提前道歉 :)
我正在计划一个读取文件并将每一行发送到频道的制作人,例如:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
processingChan <- scanner.Text()
}
并添加一些 goroutines 来消耗这些行。
现在,我想要的是,如果任何行无法在 goroutine 中处理(假设该行包含我的业务规则的无效值),我想停止生产者循环,关闭文件(已延迟) 并完成程序。
问题是:我如何"notify"生产者loop/for停止?
我发现有人建议:
for scanner.Scan() {
select {
case <- quit:
// break / return
default:
// send next line to channel
}
}
并且消费者 goroutine 将写入 "quit"(或错误)通道以防出现任何故障。
这种方法可能解决了问题,但我想知道是否有 cleaner/better 或 common/popular 方法。
我想你回答了你自己的问题。在我看来,这是最干净、最惯用的方法。而且我认为大多数 Gophers 都会同意。其他选项是通过一些变量共享状态,您必须将其包装在 Mutex
中,您仍然有一个类似的 for 循环,尽管它在检查标志的顶部或底部有一个 if看看它是否应该中止。
我认为在创建消费者和生产者结构时,最好的设计是明确命名打算在 Go 例程中 运行 的方法,例如 ReadFilesAsync
并定义退出 and/or 中止同一结构上的通道。它为您 类 的消费者提供了一种干净、简单、一致的交互方式。如果该方法是异步的,您可以在 go 例程中调用它。如果你想停止它,你调用方法的对象也会公开并中止通道,你可以发出信号来做到这一点。这消除了对样板代码的需要,例如在调用范围中声明中止通道并将其传递给异步方法。
编辑:请注意,这更多的是关于停止 goroutine 而不是停止 for。这只是输入 break
或 return
的问题。仅当 for 循环在 goroutine 中 运行ning 时才需要协调。
我觉得自己不是专家,但我觉得你的方法很好。我可以想象 goroutine 的一些不同方法。 goroutines 的输入将是符文通道。要求停止可能是关闭输入通道,结果可能是每次迭代的结果。
但在这种简单的情况下,它可能比您的代码更长更慢,因此我认为您的代码写得很好。
正确,使用 quit 频道。特别是当您已经在循环中发送到频道时,处理额外的频道很容易。但是,我不会使用您建议的表格,而是更简单 更安全 的版本:
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
效果很好。
我知道有人想使用第一个选项来确保他们尽早退出,但如果您在退出前发送一两个额外的项目进行处理,这通常不是什么大问题。
我是 Go 的新手,所以如果我的问题的答案很明显,我提前道歉 :)
我正在计划一个读取文件并将每一行发送到频道的制作人,例如:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
processingChan <- scanner.Text()
}
并添加一些 goroutines 来消耗这些行。
现在,我想要的是,如果任何行无法在 goroutine 中处理(假设该行包含我的业务规则的无效值),我想停止生产者循环,关闭文件(已延迟) 并完成程序。
问题是:我如何"notify"生产者loop/for停止?
我发现有人建议:
for scanner.Scan() {
select {
case <- quit:
// break / return
default:
// send next line to channel
}
}
并且消费者 goroutine 将写入 "quit"(或错误)通道以防出现任何故障。
这种方法可能解决了问题,但我想知道是否有 cleaner/better 或 common/popular 方法。
我想你回答了你自己的问题。在我看来,这是最干净、最惯用的方法。而且我认为大多数 Gophers 都会同意。其他选项是通过一些变量共享状态,您必须将其包装在 Mutex
中,您仍然有一个类似的 for 循环,尽管它在检查标志的顶部或底部有一个 if看看它是否应该中止。
我认为在创建消费者和生产者结构时,最好的设计是明确命名打算在 Go 例程中 运行 的方法,例如 ReadFilesAsync
并定义退出 and/or 中止同一结构上的通道。它为您 类 的消费者提供了一种干净、简单、一致的交互方式。如果该方法是异步的,您可以在 go 例程中调用它。如果你想停止它,你调用方法的对象也会公开并中止通道,你可以发出信号来做到这一点。这消除了对样板代码的需要,例如在调用范围中声明中止通道并将其传递给异步方法。
编辑:请注意,这更多的是关于停止 goroutine 而不是停止 for。这只是输入 break
或 return
的问题。仅当 for 循环在 goroutine 中 运行ning 时才需要协调。
我觉得自己不是专家,但我觉得你的方法很好。我可以想象 goroutine 的一些不同方法。 goroutines 的输入将是符文通道。要求停止可能是关闭输入通道,结果可能是每次迭代的结果。
但在这种简单的情况下,它可能比您的代码更长更慢,因此我认为您的代码写得很好。
正确,使用 quit 频道。特别是当您已经在循环中发送到频道时,处理额外的频道很容易。但是,我不会使用您建议的表格,而是更简单 更安全 的版本:
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
效果很好。
我知道有人想使用第一个选项来确保他们尽早退出,但如果您在退出前发送一两个额外的项目进行处理,这通常不是什么大问题。