Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly use sync.Cond?

I'm having trouble figuring out how to correctly use sync.Cond. From what I can tell, a race condition exists between locking the Locker and invoking the condition's Wait method. This example adds an artificial delay between the two lines in the main goroutine to simulate the race condition:

package main  import (     "sync"     "time" )  func main() {     m := sync.Mutex{}     c := sync.NewCond(&m)     go func() {         time.Sleep(1 * time.Second)         c.Broadcast()     }()     m.Lock()     time.Sleep(2 * time.Second)     c.Wait() } 

[Run on the Go Playground]

This causes an immediate panic:

fatal error: all goroutines are asleep - deadlock!  goroutine 1 [semacquire]: sync.runtime_Syncsemacquire(0x10330208, 0x1)     /usr/local/go/src/runtime/sema.go:241 +0x2e0 sync.(*Cond).Wait(0x10330200, 0x0)     /usr/local/go/src/sync/cond.go:63 +0xe0 main.main()     /tmp/sandbox301865429/main.go:17 +0x1a0

What am I doing wrong? How do I avoid this apparent race condition? Is there a better synchronization construct I should be using?


Edit: I realize I should have better explained the problem I'm trying to solve here. I have a long-running goroutine that downloads a large file and a number of other goroutines that need access to the HTTP headers when they are available. This problem is harder than it sounds.

I can't use channels since only one goroutine would then receive the value. And some of the other goroutines would be trying to retrieve the headers long after they are already available.

The downloader goroutine could simply store the HTTP headers in a variable and use a mutex to safeguard access to them. However, this doesn't provide a way for the other goroutines to "wait" for them to become available.

I had thought that both a sync.Mutex and sync.Cond together could accomplish this goal but it appears that this is not possible.

like image 567
Nathan Osman Avatar asked Apr 26 '16 06:04

Nathan Osman


People also ask

What is sync Cond?

Cond in Golang's sync package implements a conditional variable that can be used in scenarios where multiple Readers are waiting for a shared resource ready (if there is only one read and one write, a lock or channel takes care of it).

What is Cond Golang?

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.

What is sync mutex?

A Mutex is a method used as a locking mechanism to ensure that only one Goroutine is accessing the critical section of code at any point of time. This is done to prevent race conditions from happening. Sync package contains the Mutex. Two methods defined on Mutex. Lock.

What is sync in Golang?

We can make use of channels if we want to synchronize goroutines. By synchronizing, we want to make the goroutines work in a defined manner, for example, not starting the next goroutine until the previous one has finished its execution.


2 Answers

You need to make sure that c.Broadcast is called after your call to c.Wait. The correct version of your program would be:

package main  import (     "fmt"     "sync" )  func main() {     m := &sync.Mutex{}     c := sync.NewCond(m)     m.Lock()     go func() {         m.Lock() // Wait for c.Wait()         c.Broadcast()         m.Unlock()     }()     c.Wait() // Unlocks m, waits, then locks m again     m.Unlock() } 

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

like image 28
eric chiang Avatar answered Oct 14 '22 01:10

eric chiang


OP answered his own, but did not directly answer the original question, I am going to post 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() } 

Playground

Having said that, 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.

like image 99
garbagecollector Avatar answered Oct 14 '22 01:10

garbagecollector