Hi, I’m Haohongfan.

This article takes an in-depth look at Sync.cond from a source perspective. Sync. Cond is probably one of the fewer ways we can control concurrency in the day-to-day development of Go, since it’s replaced by channels in most scenarios. Also, sync.Cond is quite complicated to use.

Take the following code for example:

package main

import (
	"fmt"
	"time"
)

func main(a) {
	done := make(chan int.1)

	go func(a) {
		time.Sleep(5 * time.Second)
		done <- 1
	}()

	fmt.Println("waiting")
	<-done
	fmt.Println("done")}Copy the code

This can also be done using sync.cond

package main

import (
	"fmt"
	"sync"
	"time"
)

func main(a) {
	cond := sync.NewCond(&sync.Mutex{})
	var flag bool
	go func(a) {
		time.Sleep(time.Second * 5)
		cond.L.Lock()
		flag = true
		cond.Signal()
		cond.L.Unlock()
	}()

	fmt.Println("waiting")
	cond.L.Lock()
	for! flag { cond.Wait() } cond.L.Unlock() fmt.Println("done")}Copy the code

Channel is more convenient than sync.cond in most scenarios. Note, however, that Sync. Cond provides a Broadcast method that notifies all waiters. It’s not easy to implement this method using a channel. I think that’s the only place sync.Cond is useful.

Let’s start with a list of questions that you can read this article with:

  1. Cond. Wait is itself a blocking state. Why does cond.Wait need to be in a loop?
  2. How does sync.Cond trigger a panic that cannot be replicated?
  3. Why sync.Cond cannot be copied?
  4. How does cond.Signal notify a waiting Goroutine?
  5. How does Broadcast notify waiting goroutines?

Source analysis

Is cond.Wait blocked? How is it blocked?

It’s blocked. But not as blocking as sleep.

Call goparkunlock to unbind the m of the current goroutine and switch the current state machine to the wait state. Wait for the goReady function to restore the scene.

How does cond.Signal notify a waiting Goroutine?

  1. Check if there are any goroutines that have been awakened, and if so, return them
  2. Add 1 to the number of notified Goroutines
  3. From the goroutine queue waiting to wake up, get the Goroutine to which the head pointer points and rejoin it to the schedule
  4. A blocked Goroutine can continue execution

How does Broadcast notify waiting goroutines?

  1. Check if there are any goroutines that have been awakened, and if so, return them
  2. Set the number of goroutines waiting for notification to be equal to the number of goroutines already notified
  3. Traverse the queue of goroutines waiting to be awakened and rejoin all waiting goroutines
  4. All blocked Goroutines can continue execution

Cond. Wait is itself a blocking state. Why does cond.Wait need to be in a loop?

Notice that cond.Wait is called in the for mode instead of the if statement.

This is because when the WAIT function is awakened, there are false awakening and other conditions, resulting in the discovery that the condition is still not valid after being awakened. So you use the for statement to wait in a loop until the condition is true.

Pay attention in use

1. Call cond.Wait without locking

func (c *Cond) Wait(a) {
	c.checker.check()
	t := runtime_notifyListAdd(&c.notify)
	c.L.Unlock()
	runtime_notifyListWait(&c.notify, t)
	c.L.Lock()
}
Copy the code

We see that c.L.nunlock () is called inside Wait to release the lock first. If the caller does not lock it first, fatal Error: Sync: Unlock of unlocked mutex is triggered. For more information on how to use mutex, read “This is probably the easiest Go Mutex Source Code Analysis”.

2. Why can’t sync.Cond be copied?

The reason sync.Cond cannot be copied is not because sync.Cond is nested with Locker. Because of the Mutex/RWMutex pointer passed in NewCond, there is no problem with Mutex pointer replication.

The main reason is that sync.cond internally maintains a notifyList. If the queue is replicated, then the notifyList.wait and notifyList.notify are not the same in the concurrent scenario that causes different Goroutines to operate, resulting in some goroutines being blocked all the time.

Cond cannot be copied. Can the following code trigger the panic?

package main

import (
	"fmt"
	"sync"
)

func main(a) {
	cond1 := sync.NewCond(new(sync.Mutex))
	cond := *cond1
	fmt.Println(cond)
}
Copy the code

Those interested can give it a try and see how to trigger this panic ‘sync.Cond is copied’.

Welcome to follow my public account: HHFCodeRv