golang idiomatic way to stop a for

huangapple go评论79阅读模式
英文:

golang idiomatic way to stop a for

问题

我对Go语言还不熟悉,所以如果我的问题的答案很明显的话,请提前原谅 golang idiomatic way to stop a for

我计划编写一个生产者程序,它会读取一个文件,并将每一行发送到一个通道中,就像这样:

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    processingChan <- scanner.Text()
}

然后,我想在消费者协程中处理这些行。现在,我想要的是,如果在某个协程中处理任何一行失败(比如说,该行包含了一个不符合我的业务规则的无效值),我希望停止生产者循环,关闭文件(已经使用defer延迟关闭了),并结束程序。

我的问题是:我如何“通知”生产者循环/for循环停止?

我找到了有人建议的方法:

for scanner.Scan() {
    select {
    case <-quit:
        // break / return
    default:
        // send next line to channel
    }
}

消费者协程在出现任何错误时会向一个“quit”(或者错误)通道写入数据。

这种方法可能解决了问题,但我想知道是否有更简洁/更好或者更常见/流行的方法。

英文:

I'm new to Go so I apologize in advance if the answer to my question is obvious golang idiomatic way to stop a for

I'm planning a producer that reads a file and send each line to a channel, like:

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    processingChan &lt;- scanner.Text()
}

and add some goroutines to consume the lines.

now, what I want is that if ANY line fails to process in a goroutine (let's say the line contains an invalid value for my business rules), I want to stop the producer loop, close the file (already defered) and finish the program.

the question is: how can I "notify" the producer loop/for to stop?

I found someone suggesting:

for scanner.Scan() {
    select {
    case &lt;- quit:
        // break / return
    default:
        // send next line to channel
    }
}

and the consumer goroutines would write to a "quit" (or error) channel in case of any fault.

this approach possibly solves the question, but I wonder if there is a cleaner/better or just common/popular approach.

答案1

得分: 4

正确,使用_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
    }
}

Boom!主例程试图发送到prod通道,但没有人接收它;我们的消费者也有同样的问题。

给通道添加缓冲区也无法解决问题,但可以减少问题发生的可能性。

将前面的示例与以下更改进行比较:

select {
case <-quit:
    fmt.Println(n)
    return
case prod <- n:
}

工作得很好。

我理解有人希望使用第一种选项,以确保尽早退出,但在退出之前发送一两个额外的项目进行处理通常不是一个很大的问题。

英文:

Correct, use the quit channel. Especially as you're already sending to the channel in the loop, handling additional one is easy. However, I wouldn't use the form you proposed, but simpler and safer version:

for scanner.Scan() {
    select {
    case &lt;- quit:
        return
    case processingChan &lt;- scanner.Text():
    }
}

Why is it safer? Because it doesn't deadlock, contrary to your example with default. You might be lucky and never encounter it, but there're scenarios where you will. The problem lies in the fact you have two routines talking to each other, which always needs a little bit more of attention. Consider this:

quit := make(chan error, 1)
prod := make(chan int)

go func() {
	for n := range prod {
		runtime.Gosched()
		if n%66 == 0 {
			quit &lt;- errors.New(&quot;2/3 of evil&quot;)
			return
		}
	}
}()

for n := 1; n &lt; 1000; n++ {
	select {
	case &lt;-quit:
		fmt.Println(n)
		return
	default:
		prod &lt;- n
	}
}

// https://play.golang.org/p/3kDRAAwaKR

Boom! Main routine is trying to send to prod channel, but there is nobody to receive it; the same issue with our consumer.

Adding buffers to the channels won't solve the problem either, but would make it less likely.

Compare the previous example with the following change:

select {
case &lt;-quit:
	fmt.Println(n)
	return
case prod &lt;- n:
}

// https://play.golang.org/p/pz8DMYdrVV

Works nicely.

I understand one would like to use the first option to make sure they're quitting as early as possible, but it's usually not a massive issue if you send one or two additional items for processing before exiting.

答案2

得分: 0

我认为你已经回答了自己的问题。在我看来,这是最清晰、最符合惯用法的方法。我认为大多数 Gopher(Go 语言开发者)会同意这种观点。另一种选择是通过某个变量共享状态,你需要在其中使用 Mutex 进行包装,虽然在顶部或底部会有一个 if 语句来检查标志位,以确定是否应该中止。

我认为,在创建消费者和生产者结构体时,最好的设计是明确命名那些打算在 Go 协程中运行的方法,比如 ReadFilesAsync,并在同一个结构体上定义退出和/或中止通道。这为你的类的使用者提供了一个清晰、简单、一致的交互方式。如果方法是异步的,你可以在一个 Go 协程中调用它。如果你想要停止它,你调用方法的对象还会暴露一个中止通道,你可以在上面发送信号来实现停止。这消除了在调用范围内声明中止通道并将其传递给异步方法的样板代码的需要。

编辑:请注意,这更多地是关于停止一个 goroutine,而不是停止一个 for 循环。要停止一个 for 循环,只需使用 breakreturn。只有在该 for 循环在一个 goroutine 中运行时,才需要协调的需求。

英文:

I think you answered your own question. In my opinion, it is the cleanest most idiomatic approach. And I think most Gophers would agree. The other options is to share state through some variable which you'd have to wrap in a Mutex, you'd still have a similar for loop though it would have an if at the top or bottom where it checks the flag to see if it's supposed to abort.

I think when creating consumer and producer structs the best design is to clearly name the methods that are intended to run in Go routines like ReadFilesAsync and define the quit and/or abort channel on the same struct. It gives consumers of your classes a clean, simple, consistent thing to interact with. If the method is async, you call it within a go routine. If you want to stop it, the object you called the method on also exposes and abort channel you can signal on to do that. This removes the need for boiler plate code like declaring the abort channel in the calling scope and passing it into the asynchronous method.

EDIT: note this is more about stopping a goroutine than it is about stopping a for. That's just a matter of putting in break or return. The need for coordination only exists if that for loop is running in a goroutine.

答案3

得分: 0

我不觉得自己是专家,但我认为你的方法很好。我可以想象一种稍微不同的方法,使用goroutine。goroutine的输入可以是一个rune通道。然后停止的请求可以关闭输入通道,并且结果可以是每次迭代的结果。

但在这种简单的情况下,这种方法可能会更长,更慢,所以我认为你写的代码很好。

英文:

I don't feel like expert, but I think your approach is good. I could imagine bit different approach with goroutine. The input of goroutines would be a channel of runes. Than ask to stop could be close an input channel and result could be result of each iteration.

But in this simple case it could be probably longer and slower than your code, therefore I thing you write a good code.

huangapple
  • 本文由 发表于 2015年10月29日 04:00:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/33400530.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定