英文:
How can I unit test a goroutine that runs on an infinite for loop using sync.Cond?
问题
我正在尝试对一个在无限循环中运行的通道进行单元测试。我认为我找到了一种方法来做到这一点,但我不确定是否这是一种有效使用条件变量的方式。而且我也不确定这种方法是否容易出现竞态条件。由于这个无限循环在自己的goroutine中运行,所以在我到达"cond.Wait()"的时候,通道是否可能已经被耗尽了呢?如果发生这种情况,我会永远挂起吗?在我看到的所有使用条件变量的例子中,它们通常都伴随着一个包围等待的for循环。我需要在这里使用吗?我的问题是:我在这里使用的方法有什么问题吗?这是一种有效/惯用的条件变量的使用方式吗?
package main
import (
"fmt"
"sync"
)
var doStuffChan chan bool
var cond *sync.Cond
var result string
func main() {
doStuffChan = make(chan bool, 10)
cond = &sync.Cond{L: &sync.Mutex{}}
go startDoStuffLoop()
doStuffChan <- true
cond.L.Lock()
cond.Wait()
cond.L.Unlock()
fmt.Println(result)
}
func startDoStuffLoop() {
for {
<-doStuffChan
result = "success"
cond.Broadcast()
}
}
这段代码的问题在于,当你调用cond.Wait()
时,你没有在循环中等待条件的变化。这可能导致你的程序在某些情况下永远挂起。为了解决这个问题,你可以将cond.Wait()
放在一个for循环中,以确保在满足条件之前一直等待。修改后的代码如下:
package main
import (
"fmt"
"sync"
)
var doStuffChan chan bool
var cond *sync.Cond
var result string
func main() {
doStuffChan = make(chan bool, 10)
cond = &sync.Cond{L: &sync.Mutex{}}
go startDoStuffLoop()
doStuffChan <- true
cond.L.Lock()
for result == "" {
cond.Wait()
}
cond.L.Unlock()
fmt.Println(result)
}
func startDoStuffLoop() {
for {
<-doStuffChan
result = "success"
cond.Broadcast()
}
}
现在,当你调用cond.Wait()
时,它会在一个for循环中等待,直到result
的值被修改为非空。这样,你就可以避免永远挂起的情况。这是一种常见的使用条件变量的方式。
英文:
I am trying to unit test a channel that runs on an infinite for loop. I think I've found a way to do it, but I'm not sure if it's a valid way to use conditional variables. Also I'm not sure if this approach is prone to a race condition. Since the for loop is running on its own goroutine, is it possible the channel would be drained by the time I get to "cond.Wait()?" If this happened would I hang forever? In all the examples I've seen using conditional variables they are usually accompanied by a for loop surrounding the wait. Do I need this here? My question: is there anything wrong with the approach I'm using here? Is this a valid/idiomatic use of conditional variables?
package main
import (
"fmt"
"sync"
)
var doStuffChan chan bool
var cond *sync.Cond
var result string
func main() {
doStuffChan = make(chan bool, 10)
cond = &sync.Cond{L: &sync.Mutex{}}
go startDoStuffLoop()
doStuffChan <- true
cond.L.Lock()
cond.Wait()
cond.L.Unlock()
fmt.Println(result)
}
func startDoStuffLoop() {
for {
<-doStuffChan
result = "success"
cond.Broadcast()
}
}
答案1
得分: 2
在我看来,你对所有的假设都是正确的。为了避免通道耗尽,只需使用
close(doStuffChan)
而不是 doStuffChan <- true
,因为你可以永远从关闭的通道接收到 nil。然后在 Wait 周围加上循环,以检查在 cond 成为 true 之前是否已经满足条件,因为在大多数情况下这是一个条件。如果你不想关闭通道,可以使用通道保护信号,并使用锁进行广播,这样可以确定操作的优先级。
func main() {
doStuffChan = make(chan bool)
cond = &sync.Cond{L: &sync.Mutex{}}
go startDoStuffLoop()
cond.L.Lock()
doStuffChan <- true
cond.Wait()
cond.L.Unlock()
fmt.Println(result)
}
func startDoStuffLoop() {
for {
<-doStuffChan
result = "success"
cond.L.Lock()
cond.Broadcast()
cond.L.Unlock()
}
}
查看它的工作原理:https://play.golang.org/p/1S6VW7nIoV 两个版本都是线程安全的。
英文:
To my mind you are right in all of your assumptions. To avoid channel draining just use
close(doStuffChan)
instead of doStuffChan <- true
, because you can receive nil from closed channel forever.
Them surround Wait with loop to check before cond been true since it's a condition in most cases. If you don't want to close channel guard signalling in channel and broadcasting with Lock, which make operation precedence deterministic.
func main() {
doStuffChan = make(chan bool)
cond = &sync.Cond{L: &sync.Mutex{}}
go startDoStuffLoop()
cond.L.Lock()
doStuffChan <- true
cond.Wait()
cond.L.Unlock()
fmt.Println(result)
}
func startDoStuffLoop() {
for {
<-doStuffChan
result = "success"
cond.L.Lock()
cond.Broadcast()
cond.L.Unlock()
}
}
See it works https://play.golang.org/p/1S6VW7nIoV Both versions are threadsafe however.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论