【问题标题】:Loop to check condition in concurrent program循环检查并发程序中的条件
【发布时间】:2020-01-08 07:43:42
【问题描述】:

我正在阅读一本关于 Go 并发的书(我现在正在学习它),我发现了这段代码:

c  :=  sync.NewCond(&sync.Mutex{})
queue  :=  make([]interface{},  0,  10)
removeFromQueue  :=  func(delay  time.Duration) {
    time.Sleep(delay)
    c.L.Lock()
    queue  =  queue[1:]
    fmt.Println("Removed from queue")
    c.L.Unlock() c.Signal()
} 

for  i  :=  0;  i  <  10;  i++ {
    c.L.Lock()

    // Why this loop?
    for  len(queue)  ==  2 {
        c.Wait()
    }

    fmt.Println("Adding to queue")
    queue  =  append(queue,  struct{}{})
    go  removeFromQueue(1*time.Second)
    c.L.Unlock()
}

问题是我不明白作者为什么要引入注释标记的for循环。据我所知,如果没有它,程序是正确的,但作者说循环存在是因为Cond 会发出信号表示仅发生了某些事情,但这并不意味着状态已经真正改变了。

在什么情况下可能?

【问题讨论】:

  • 这是哪本书?
  • "没有它,程序是正确的" 程序什么都不做,所以它不可能是“正确的”。我认为代码可以展示 Signal 和 Wait 如何协同工作。此代码是 Signal and Wait only 的示例,其余代码用于展示这些函数如何工作。如果没有它们,其余的代码就不会工作。它的存在只允许 Signal 和 Wait 存在。
  • 你确定 for 循环的代码没有在单独的 goroutine 中执行吗?
  • @kostix 代码完全按照书中的内容复制。
  • @marco.m Katherine Cox-Buday 撰写的“Go 并发”。

标签: go concurrency goroutine


【解决方案1】:

手头没有实际的书,而只是一些看似断章取义的代码 sn-ps,很难说出作者的具体想法。但我们可以猜测。在包括 Go 在内的大多数语言中,关于条件变量都有一个普遍的观点:等待某个条件得到满足确实需要一个循环一般来说。在某些特定情况下,不需要循环。

我认为 Go 文档对此更清楚。特别是,syncfunc (c *Cond) Wait() 的文字描述说:

Wait 原子地解锁 c.L 并暂停调用 goroutine 的执行。稍后恢复执行后,Wait 在返回之前锁定 c.L。与其他系统不同,Wait 不能返回,除非被广播或信号唤醒。

因为当 Wait 第一次恢复时 c.L 没有被锁定,所以调用者通常不能假设 Wait 返回时条件为真。相反,调用者应该循环等待:

c.L.Lock()
for !condition() {
    c.Wait()
}
... make use of condition ...
c.L.Unlock()

我在解释循环原因的短语上加了粗体强调。

是否可以省略循环取决于不止一件事:

  • 另一个 goroutine 在什么条件下调用 Signal 和/或 Broadcast
  • 有多少个 goroutine 正在运行,它们可能并行执行什么?

正如 Go 文档所说,在 Go 中我们不必担心一种情况,而在其他一些系统中我们可能会担心。在某些系统中,当Signal(或其等效项)实际上并未在条件变量上调用时,有时会恢复(通过Signal 的等效项)等效项Wait

您引用的queue 示例特别奇怪,因为只有一个goroutine(运行for 循环的那个)可以添加 条目到队列中。其余的 goroutine 仅 删除 个条目。因此,如果队列长度为 2,并且我们暂停并等待队列长度已更改的信号,则队列长度只能更改为 1 或 0:没有其他 goroutine 可以添加并且只有我们此时创建的两个 goroutine 可以从中删除。这意味着在给定这个特定示例的情况下,我们遇到了一种根本不需要循环的情况。

(奇怪的是queue的初始容量是10,这是我们要放入的项目,然后我们开始等待它的长度正好是2,所以我们不应该到达无论如何,如果我们要剥离可能添加到队列中的其他 goroutine,则在 len(queue) == 2 时等待的循环确实可以通过将计数从 2 降至 1 但没有机会恢复的删除来发出信号,直到插入发生,将计数推回到 2。但是,根据情况,直到 两个 其他 goroutine 每个添加了一个条目,该循环可能不会恢复。例如,计数到 3。那么当长度正好为 2 时为什么要重复循环?如果我们的想法是保留队列槽,我们应该在计数大于或等于 2 时循环。)

(除此之外,初始容量无关紧要,因为队列会在必要时动态调整大小。)

【讨论】:

    猜你喜欢
    • 2013-06-24
    • 2010-12-16
    • 1970-01-01
    • 2017-04-03
    • 1970-01-01
    • 1970-01-01
    • 2019-09-04
    • 2021-08-09
    • 2015-09-22
    相关资源
    最近更新 更多