I was reading through the Go concurrency book, and I came across the following example.
func condVarBroadcast() {
type button struct {
clicked *sync.Cond
}
b := button{sync.NewCond(&sync.Mutex{})}
// for each call to subscribe, start up a new goroutine
// and wait using the condition variable.
subscribe := func(c *sync.Cond, fn func()) {
var w sync.WaitGroup
w.Add(1)
go func() {
w.Done()
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
w.Wait()
}
var wg sync.WaitGroup
wg.Add(3)
subscribe(b.clicked, func() {
fmt.Println("Maximize window")
wg.Done()
})
subscribe(b.clicked, func() {
fmt.Println("displaying dialog box")
wg.Done()
})
subscribe(b.clicked, func() {
fmt.Println("mouse clicked")
wg.Done()
})
b.clicked.Broadcast()
wg.Wait()
}
I had a couple of doubts:
w.Done()
inside a goroutine when it clearly is not done executing? Is this a normal practice?w
in this example? If I don't use it, it says "deadlock: all goroutines are asleep.Here the problem with race condition is that the main is super fast and broadcasts to the sync.Cond
even before the go-routines start and wait for the lock. The reason to have w
waitgroup is to make sure that all the go-routines start first and hold the lock before the main thread signals the broadcast
func condVarBroadcast() {
type button struct {
clicked *sync.Cond
}
b := button{sync.NewCond(&sync.Mutex{})}
// for each call to subscribe, start up a new goroutine
// and wait using the condition variable.
subscribe := func(c *sync.Cond, fn func()) {
var w sync.WaitGroup
w.Add(1)
go func() {
w.Done() // <-------- this will notify that the goroutine has started
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
}()
w.Wait() // <--- this will block till the go routine starts and then continue
}
var wg sync.WaitGroup
wg.Add(3)
subscribe(b.clicked, func() {
fmt.Println("Maximize window")
wg.Done()
}) // < ----- This call will block till the go routine starts
subscribe(b.clicked, func() {
fmt.Println("displaying dialog box")
wg.Done()
}) // < ----- This call will block till the go routine starts
subscribe(b.clicked, func() {
fmt.Println("mouse clicked")
wg.Done()
}) // < ----- This call will block till the go routine starts
b.clicked.Broadcast() // <------------ Once this happens all the go routines will continue the processing
wg.Wait()
}
If you want to avoid the w
waitgroup, you have to somehow make sure the Broadcast
call happens after all the go routines start in that case you could add a simple time.Sleep(time.Second * 1)
before the Broadcast
call and remove the w
waitgroup from the code.