all go routines are asleep while using sync.Cond

Viewed 137
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()
            c.L.Lock()
            defer c.L.Unlock()
            c.Wait()
            fn()
        }()
        //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 dialogue box!")
        clickRegistered.Done()
    })
    subscribe(button.Clicked, func() {
        fmt.Println("Mouse clicked.")
        clickRegistered.Done()
    })

    button.Clicked.Broadcast()
    clickRegistered.Wait()
}

I am following some examples from concurrency in go book, and I have commented some code statements inside subscribe method. can someone please help in explaining why this code deadlocks.

1 Answers

a note : as far as naming goes, clickRegistered doesn't fit the current behavior of your code. handlerExecuted would be more accurate.


One way to fix the deadlock :

starting from your code : https://play.golang.org/p/XEduyON9j59

You may pass an extra waitGroup or a channel to your goroutines, so that they can signal the main goroutine that they are running, but actually you can also use the condition itself to do that :

    subscribe := func(c *sync.Cond, fn func()) {
        c.L.Lock()  // take the lock synchronously, the lock will be available
                    // again once the goroutine executes 'c.Wait()'
        go func() {
            defer c.L.Unlock()
            c.Wait()
            fn()
        }()
    }

    subscribe(...)
    subscribe(...)
    subscribe(...)

    button.Clicked.L.Lock() // will return only once all subscribers are '.Wait()'ing

https://play.golang.org/p/fFzRolUnaDZ


Here is another way to fix the deadlock, using a Waitgroup to let the goroutines signal that they are running:
https://play.golang.org/p/LSM72HBmo0M

The way the subscriptions are run may feel more "concurrent"; you will note, however, that the only straightforward way to know that they are actually running their .Wait() instruction is still to acquire the condition's lock.

Related