Home > Net >  Understanding sync.cond in golang
Understanding sync.cond in golang

Time:01-19

I have started learning golang recently and I came across this sync.Cond function. I have little trouble understanding how this works.

consider the below code

var sharedRsc = make(map[string]interface{})

func main() {
    var wg sync.WaitGroup
    mu := sync.Mutex{}
    c := sync.NewCond(&mu)

    wg.Add(1)
    go func() {
        defer wg.Done()

        c.L.Lock()
        // for len(sharedRsc) == 0 {
        c.Wait()
        // }

        c.L.Unlock()
        fmt.Println(sharedRsc["rsc1"])
    }()

    // writes changes to sharedRsc
    c.L.Lock()
    sharedRsc["rsc1"] = "foo"
    c.Signal()
    c.L.Unlock()

    wg.Wait()
}

when this is executed it leads to a deadlock. I understand the reason might be c.Signal is called before c.Wait.

But what I don't understand is when we uncomment the for loop it works. So how does the for loop solves this race condition? Even here there is a possibility c.Signal can be called before c.Wait right?

CodePudding user response:

You will need the for loop because if the cond is signaled before the second coroutine waiting, it will deadlock at the Wait() because no one will signal after that.

Consider the following execution flow:

  • Modify sharedRsc
  • c.Signal()
  • wg.Wait()
  • c.Wait()

CodePudding user response:

This is how to correctly use sync.Cond.

You do not really need sync.Cond if you have one goroutine for each write and read - a single sync.Mutex would suffice to communicate between them. sync.Cond could useful in situations where multiple readers wait for the shared resources to be available.

var sharedRsc = make(map[string]interface{})
func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    m := sync.Mutex{}
    c := sync.NewCond(&m)
    go func() {
        // this go routine wait for changes to the sharedRsc
        c.L.Lock()
        for len(sharedRsc) == 0 {
            c.Wait()
        }
        fmt.Println(sharedRsc["rsc1"])
        c.L.Unlock()
        wg.Done()
    }()

    go func() {
        // this go routine wait for changes to the sharedRsc
        c.L.Lock()
        for len(sharedRsc) == 0 {
            c.Wait()
        }
        fmt.Println(sharedRsc["rsc2"])
        c.L.Unlock()
        wg.Done()
    }()

    // this one writes changes to sharedRsc
    c.L.Lock()
    sharedRsc["rsc1"] = "foo"
    sharedRsc["rsc2"] = "bar"
    c.Broadcast()
    c.L.Unlock()
    wg.Wait()
}

You can it in Go Playground.

Using channels is still the recommended way to pass data around if the situation permitting.

Note: sync.WaitGroup here is only used to wait for the goroutines to complete their executions.

For better understanding refer the official docs : https://pkg.go.dev/sync

And also see the similar SO : How to correctly use sync.Cond?

  •  Tags:  
  • Related