【问题标题】:Using channel or sync.Cond to wait for condition使用 channel 或 sync.Cond 等待条件
【发布时间】:2017-06-04 17:36:22
【问题描述】:

我正在尝试等待一个特定的条件,我想知道如何最好地做到这一点。我有一个看起来像这样的结构(简化):

type view struct {
    timeFrameReached bool
    Rows []*sitRow
}

在 goroutine 中,我正在更新一个文件,该文件被读入 view 变量。行数增加,timeFrameReached 最终将是true

在其他地方,我想等待以下条件成立:

view.timeFrameReached == true || len(view.Rows) >= numRows

我正在尝试学习通道以及 Go 的条件变量如何工作,我想知道这里最好的解决方案是什么。理论上,我可以做一些像这样微不足道的事情:

for {
    view = getView()
    if view.timeFrameReached == true || len(view.Rows) >= numRows {
        break
    }
}

但这显然是一个幼稚的解决方案。 numRows 的值来自 HTTP 请求,因此条件方法似乎具有挑战性。 goroutine 不知道何时广播条件,因为它不知道它正在寻找的行数。

【问题讨论】:

    标签: go channel condition-variable


    【解决方案1】:

    我想我会用条件变量来做到这一点。这个概念并不是说Signal 应该只在服务员想要检查的条件为真时才执行,而是在它可能为真时(即,被检查的东西已经改变)。

    这样做的正常模式是:

    mutex.Lock()
    for {
        view = getView()
        if view.timeFrameReached == true || len(view.Rows) >= numRows {
            break
        }
        cond.Wait(&mutex)
    }
    // Do stuff with view
    mutex.Unlock()
    

    然后,在 view 被更改的代码中:

    mutex.Lock()
    // Change view
    cond.Signal() // or cond.Broadcast()
    mutex.Unlock()
    

    显然,我在不知道您的程序如何工作的情况下编写了此代码,因此您可能需要进行一些更改。

    您可以通过在通道上发送信号并尝试从通道接收等待来对通道执行类似的操作,但这对我来说似乎更复杂。 (另外,如果您有多个 goroutine 等待,您可以使用 cond.Broadcast 通知所有这些 goroutine 唤醒。)

    【讨论】:

    • 这是个好主意。我还发布了一个我想出的替代想法。
    • 虽然这是一个可行的解决方案,但经验表明使用互斥体最终是不可扩展的:它们可能需要难以封装的详细全局知识。 Goroutines 和 channels 形成可组合的元素(这是 CSP 的核心特性),这是在这种情况下尝试使用它们的原因。
    【解决方案2】:

    我的一个想法涉及通过通道传达所需的行数,构建视图的 goroutine 将执行非阻塞接收,以查看主线程是否正在请求一定数量的行。如果是,它会发回一条消息,表明条件满足。

    这里的main 函数请求行数:

    if numRows > len(viewFile.View.Rows) && !viewFile.View.TimeFrameReached {
        // Send the required number of rows
        rows <- numRows
        // Wait for the prefetch loop to signal that the view file is ready
        <-rows // Discard the response value and move on
        view = getView()
    }
    

    这里 goroutine 检查是否需要一定数量的行。如果是这样,它会在准备好时以肯定的信号进行响应。该信号的值无关紧要。

    select {
        case numRows := <-rows:
            if len(viewFile.View.Rows) >= numRows || viewFile.View.TimeFrameReached {
                rows <- 1
            }
        default:
    }
    

    【讨论】:

    • 你需要两个不同的通道,否则你不知道哪段代码会读取numRows
    • 在任何 goroutine 中使用同一通道的两端时要小心。在这种情况下,无缓冲通道通常会导致死锁。上面第一个代码sn-p有这个问题我同意上面Andy的评论。
    • 另一种方法是使用单个通道两次(如上),但在第二个实例中更改发送方向。如果它是一个无缓冲的通道,它可以用于任何方式的同步。
    • @Rick-77 改变发送方向是什么意思?我正在使用我上面的代码,它似乎正在工作。这也是这种情况的一个示例:play.golang.org/p/iFj7PuUxLt。你能帮我了解一下你们的cmets吗?
    • 第一种情况是rows &lt;- numRows,写入频道。第二种情况是&lt;-rows,从频道读取。这可能是rows &lt;- 0 或类似的;仅用作信号时,该值无关紧要
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    • 1970-01-01
    • 2013-03-02
    • 1970-01-01
    • 2015-03-13
    相关资源
    最近更新 更多