循环检查并发程序中的条件
Loop to check condition in concurrent program
我在读一本关于 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
只会发出发生某些事情的信号,但这并不意味着状态已经真正改变.
什么情况下可以?
没有手头的实际书籍,而是一些看似断章取意的代码片段,很难说出作者的具体想法。但是我们可以猜测。在包括 Go 在内的大多数语言中,关于条件变量有一个普遍的观点:等待某些条件被满足确实需要一个循环 in general。在某些特定情况下,不需要循环。
我认为 Go 文档对此更清楚。特别是 sync
的 func (c *Cond) Wait()
的文字描述说:
Wait atomically unlocks c.L and suspends execution of the calling goroutine. After later resuming execution, Wait locks c.L before returning. Unlike in other systems, Wait cannot return unless awoken by Broadcast or Signal.
Because c.L is not locked when Wait first resumes, the caller typically cannot assume that the condition is true when Wait returns. Instead, the caller should Wait in a loop:
c.L.Lock()
for !condition() {
c.Wait()
}
... make use of condition ...
c.L.Unlock()
我用粗体强调了解释循环原因的短语。
是否可以省略循环取决于不止一件事:
- 在什么情况下另一个 goroutine 调用
Signal
and/or Broadcast
?
- 有多少个 goroutines 运行,它们可能并行做什么?
正如 Go 文档所说,有一种情况我们 不必 在 Go 中担心,我们可能会在其他一些系统中担心。在某些系统中,Wait
的等价物有时会在 Signal
(或其等价物)实际上并未在条件变量上调用时恢复(通过 Signal
的等价物)。
您引用的 queue
示例特别奇怪,因为只有一个 goroutine - 运行 数到十的 for
循环 - 可以 向队列添加 个条目。其余的 goroutines 仅 remove 个条目。因此,如果队列长度为 2,并且我们暂停并等待队列长度已更改的信号,则队列长度只能更改为 1 或 0:没有其他 goroutine 可以 add 到它,只有我们此时创建的两个 goroutines 可以 删除 从它。这意味着给定 这个特定示例,我们有一种情况根本不需要循环。
(奇怪的是queue
的初始容量为10,也就是我们要放入的物品数量,然后当它的长度恰好为2时我们开始等待,这样无论如何我们都不应该达到那个容量。如果我们要分离出可能添加到队列中的其他 goroutines,等待 len(queue) == 2
的循环确实可以通过将计数从 2 降低到 1 但不是有机会恢复直到插入发生,将计数推回到 2。但是,根据情况,该循环可能不会恢复,直到 两个 其他 goroutines each 添加了一个条目,例如将计数推到 3。那么为什么当长度 恰好 二时重复循环?如果想法是保留队列槽,我们应该在计数大于或等于 2 时循环。)
(除此之外,初始容量无关紧要,因为如果需要,队列的大小将动态调整为大片。)
我在读一本关于 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
只会发出发生某些事情的信号,但这并不意味着状态已经真正改变.
什么情况下可以?
没有手头的实际书籍,而是一些看似断章取意的代码片段,很难说出作者的具体想法。但是我们可以猜测。在包括 Go 在内的大多数语言中,关于条件变量有一个普遍的观点:等待某些条件被满足确实需要一个循环 in general。在某些特定情况下,不需要循环。
我认为 Go 文档对此更清楚。特别是 sync
的 func (c *Cond) Wait()
的文字描述说:
Wait atomically unlocks c.L and suspends execution of the calling goroutine. After later resuming execution, Wait locks c.L before returning. Unlike in other systems, Wait cannot return unless awoken by Broadcast or Signal.
Because c.L is not locked when Wait first resumes, the caller typically cannot assume that the condition is true when Wait returns. Instead, the caller should Wait in a loop:
c.L.Lock() for !condition() { c.Wait() } ... make use of condition ... c.L.Unlock()
我用粗体强调了解释循环原因的短语。
是否可以省略循环取决于不止一件事:
- 在什么情况下另一个 goroutine 调用
Signal
and/orBroadcast
? - 有多少个 goroutines 运行,它们可能并行做什么?
正如 Go 文档所说,有一种情况我们 不必 在 Go 中担心,我们可能会在其他一些系统中担心。在某些系统中,Wait
的等价物有时会在 Signal
(或其等价物)实际上并未在条件变量上调用时恢复(通过 Signal
的等价物)。
您引用的 queue
示例特别奇怪,因为只有一个 goroutine - 运行 数到十的 for
循环 - 可以 向队列添加 个条目。其余的 goroutines 仅 remove 个条目。因此,如果队列长度为 2,并且我们暂停并等待队列长度已更改的信号,则队列长度只能更改为 1 或 0:没有其他 goroutine 可以 add 到它,只有我们此时创建的两个 goroutines 可以 删除 从它。这意味着给定 这个特定示例,我们有一种情况根本不需要循环。
(奇怪的是queue
的初始容量为10,也就是我们要放入的物品数量,然后当它的长度恰好为2时我们开始等待,这样无论如何我们都不应该达到那个容量。如果我们要分离出可能添加到队列中的其他 goroutines,等待 len(queue) == 2
的循环确实可以通过将计数从 2 降低到 1 但不是有机会恢复直到插入发生,将计数推回到 2。但是,根据情况,该循环可能不会恢复,直到 两个 其他 goroutines each 添加了一个条目,例如将计数推到 3。那么为什么当长度 恰好 二时重复循环?如果想法是保留队列槽,我们应该在计数大于或等于 2 时循环。)
(除此之外,初始容量无关紧要,因为如果需要,队列的大小将动态调整为大片。)