learnlearnlearn
learnlearnlearn

Reputation: 998

goroutine deadlock with waitgroup

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:

Upvotes: 0

Views: 77

Answers (1)

Neenad
Neenad

Reputation: 909

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.

Upvotes: 1

Related Questions