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()
}
}
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.