This is the 19th day of my participation in the August Wenwen Challenge.More challenges in August

What can sync.cond be used for?

Cond in Golang’s Sync package implements a conditional variable that can wait for a common resource using multiple readers.

Each Cond is associated with a Lock. When the Condition is modified or the Wait method is called, a Lock must be placed to protect the Condition. Similar to Wait and NotifyAll in Java.

The sync.Cond condition variable is used to coordinate goroutines that want to share resources, and can be used to notify gorountine that is blocked by a mutex when the state of the shared resource changes.

Differences from Sync.mutex

Sync. Cond is based on a mutex. What’s the difference?

Sync.mutex is usually used to protect critical sections and shared resources, and the condition variable sync.cond is used to coordinate shared resources that you want to access.

Sync. Cond Application scenario

One coroutine is receiving data, and the other coroutines must wait for the coroutine to finish receiving data before reading the correct data.

In this case, if a channel or mutex is used, only one coroutine can wait and read the data, and there is no way to tell the other coroutines to read the data.

What about this time?

  • A global variable can be used to indicate whether the first coroutine has finished receiving data, and the remaining coroutines check the value of the variable repeatedly until they read data.
  • Multiple channels can also be created. Each coroutine blocks on a channel, and the coroutine receiving the data is notified one by one after the data is received.

Go actually has a sync.cond built in to solve this problem.

sync.Cond

// Each Cond has an associated Locker L (often a *Mutex or *RWMutex), // which must be held when changing the condition and // when calling the Wait method. // // A Cond must not be copied after first use. type Cond struct { noCopy noCopy // L is held while observing or changing the condition L Locker notify  notifyList checker copyChecker }Copy the code

As you can see, each Cond is associated with a lock (L) (Mutex, or read/write lock * RMMutex), which must be held when modifying conditions or using Wait.

What are the methods of sync.cond

NewCond Creates an instance

func NewCond(l Locker) *Cond
Copy the code

NewCond creates an instance that requires a lock to be associated.

Specific examples:

cadence := sync.NewCond(&sync.Mutex{})
Copy the code

Broadcast Broadcast awakens all

// Broadcast wakes all goroutines waiting on c.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Broadcast()
Copy the code

Broadcast wakes up all goroutines waiting for the condition variable C without lock protection.

Specific examples:

go func() {
   for range time.Tick(1 * time.Millisecond) {
      cadence.Broadcast()
   }
}()
Copy the code

Signal wakes up a coroutine

// Signal wakes one goroutine waiting on c, if there is any.
//
// It is allowed but not required for the caller to hold c.L
// during the call.
func (c *Cond) Signal()
Copy the code

Signal only wakes up any goroutine waiting for condition variable C, without lock protection. Similar to Notify in Java

Wait Wait

// Wait atomically unlocks c.L and suspends execution // of the calling goroutine. After later resuming execution, // Wait locks c.L before returning. Unlike in other systems, // Wait cannot return unless awoken by Broadcast or Signal. // // Because c.L is not locked when Wait first resumes, the caller // typically cannot assume that the condition is true when // Wait returns. Instead, the caller should Wait in a loop: // // c.L.Lock() // for ! condition() { // c.Wait() // } // ... make use of condition ... // c.L.Unlock() // func (c *Cond) Wait()Copy the code

Calling Wait automatically releases lock c.L and suspends the caller’s goroutine, so the coroutine currently blocks where the Wait method is called. If another coroutine calls Signal or Broadcast to wake up the coroutine, the Wait method stops blocking, re-locks c.L, and continues to execute the code following Wait

Code examples:

c.L.Lock() for ! condition() { c.Wait() } ... make use of condition ... c.L.Unlock()Copy the code

Code sample

package sync import ( "log" "sync" "testing" "time" ) var done = false func read(name string, c *sync.Cond) { c.L.Lock() for ! done { c.Wait() } log.Println(name, "starts reading") c.L.Unlock() } func write(name string, c *sync.Cond) { log.Println(name, "starts writing") time.Sleep(time.Second) c.L.Lock() done = true c.L.Unlock() log.Println(name, "wakes all") c.Broadcast() } func TestSyncCond(t *testing.T) { cond := sync.NewCond(&sync.Mutex{}) go read("reader1", cond) go read("reader2", cond) go read("reader3", cond) write("writer", cond) time.Sleep(time.Second * 3) }Copy the code

The results

=== RUN TestSyncCond 2021/08/26 11:06:48 writer starts writing 2021/08/26 11:06:49 writer wakes all 2021/08/26 11:06:49 reader3 starts reading 2021/08/26 11:06:49 reader2 starts reading 2021/08/26 11:06:49 reader1 starts reading --- PASS: PASS TestSyncCond (4.01 s)Copy the code