Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

calling wait group done right after go routine starts?

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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?

>Solution :

This is a mechanism that ensures that when subscribe returns, the goroutine has started running. When goroutine starts, it calls Done to signal to the waiting caller that the goroutine is running. Without this mechanism, it is possible that when subscribe returns the goroutine has not been scheduled yet.

A deferred Done will not work, because that will only run once the goroutine returns, and that will not happen until the condition variable is signaled.

The scheme does not guarantee that the new goroutine locks the mutex. It is debatable whether this schema is really necessary.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading