https://go.dev/play/p/YVYRWSgcp4u
I'm seeing this code in "Concurrency in Go Tools and Techniques for Developers", where it's mentioned about the usage for broadcast, the context is to use broadcast to wake three goroutings.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
type Button struct {
Clicked *sync.Cond
}
button := Button{Clicked: sync.NewCond(&sync.Mutex{})}
subscribe := func(c *sync.Cond, fn func()) {
var goroutineRunning sync.WaitGroup
goroutineRunning.Add(1)
go func() {
goroutineRunning.Done() // <---- why here?
//fmt.Println("wg already done")
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
//goroutineRunning.Done(), if put here will result in deadlock, why?
}()
goroutineRunning.Wait()
}
var clickRegistered sync.WaitGroup
clickRegistered.Add(3)
subscribe(button.Clicked, func() {
fmt.Println("Maximizing window.")
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
fmt.Println("Displaying annoying dialog box!")
clickRegistered.Done()
})
subscribe(button.Clicked, func() {
fmt.Println("Mouse clicked.")
clickRegistered.Done()
})
time.Sleep(time.Second * 3)
button.Clicked.Broadcast()
clickRegistered.Wait()
}
I'm trying to understand the subscribe part
subscribe := func(c *sync.Cond, fn func()) {
var goroutineRunning sync.WaitGroup
goroutineRunning.Add(1)
go func() {
goroutineRunning.Done()
//fmt.Println("wg already done")
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
//goroutineRunning.Done()
//fmt.Println("fn executed")
}()
goroutineRunning.Wait()
}
The author says:
Here we define a convenience function that will allow us to register functions to handle signals from a condition. Each handler is run on its own goroutine, and subscribe will not exit until that goroutine is confirmed to be running.
My understanding is that we should defer goroutingRunning.Done()
inside gorouting so that the subsequent code (including waiting for Cond and fn() call, will have opportunities
to run), but in this case it seems the goroutingRunning.Done()
has to be in the beginning of gorouting, otherwise it would result in deadlock error, but why?
------UPDATE------
We could actually get rid of the waitgroup in subscribe function this way:
subscribe := func(c *sync.Cond, fn func(), wg *sync.WaitGroup) {
c.L.Lock()
defer c.L.Unlock()
c.Wait()
fn()
wg.Done()
}
var ClickRegistered sync.WaitGroup
ClickRegistered.Add(3)
go subscribe(button.Clicked, func() {
fmt.Println("do 1")
}, &ClickRegistered)
go subscribe(button.Clicked, func() {
fmt.Println("do 2")
}, &ClickRegistered)
go subscribe(button.Clicked, func() {
fmt.Println("do 3")
}, &ClickRegistered)
time.Sleep(time.Millisecond * 50)
fmt.Println("some process in main go routine finished")
button.Clicked.Broadcast()
ClickRegistered.Wait()