【问题标题】:golang idiomatic way to stop a forgolang 惯用的方式来停止 for
【发布时间】:2015-10-28 20:00:46
【问题描述】:

我是 Go 新手,所以如果我的问题的答案很明显,我提前道歉:)

我正在计划一个生产者读取文件并将每一行发送到一个频道,例如:

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

并添加一些 goroutine 来使用这些行。

现在,我想要的是,如果任何行无法在 goroutine 中处理(假设该行包含我的业务规则的无效值),我想停止生产者循环,关闭文件(已经延迟)和完成程序。

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

我发现有人建议:

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

如果出现任何故障,消费者 goroutine 将写入“退出”(或错误)通道。

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

【问题讨论】:

    标签: go goroutine


    【解决方案1】:

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

    效果很好。

    我知道有人希望使用第一个选项来确保他们尽早退出,但如果您在退出前发送一两个额外的项目进行处理,这通常不是一个大问题。

    【讨论】:

      【解决方案2】:

      我不觉得自己像专家,但我认为你的方法很好。我可以想象与 goroutine 有一点不同的方法。 goroutine 的输入将是一个符文通道。要求停止可以关闭输入通道,结果可能是每次迭代的结果。

      但在这种简单的情况下,它可能比您的代码更长且更慢,因此我认为您编写的代码很好。

      【讨论】:

        【解决方案3】:

        我想你回答了你自己的问题。在我看来,这是最干净最惯用的方法。我认为大多数 Gophers 都会同意。其他选项是通过一些变量共享状态,您必须将其包装在Mutex 中,尽管它在顶部或底部有一个 if 在检查标志以查看如果它应该中止。

        我认为在创建消费者和生产者结构时,最好的设计是明确命名打算在 Go 例程中运行的方法,例如 ReadFilesAsync,并在同一结构上定义退出和/或中止通道。它为您的类的消费者提供了一个干净、简单、一致的交互方式。如果方法是异步的,则在 go 例程中调用它。如果您想停止它,您调用该方法的对象也会公开并中止您可以发出信号来执行此操作的通道。这消除了对样板代码的需要,例如在调用范围内声明中止通道并将其传递给异步方法。

        编辑:请注意,这更多是关于停止 goroutine 而不是停止 for。这只是输入breakreturn 的问题。只有当 for 循环在 goroutine 中运行时,才需要协调。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-24
          相关资源
          最近更新 更多