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).
Cond pooling point: multiple goroutines waiting, 1 goroutine notification event occurs.
Each Cond is associated with a Lock (
*sync.Mutex or *sync.RWMutex), which must be added when modifying conditions or calling Wait methods, protecting the condition.
Create a new Cond conditional variable.
Broadcast will wake up all goroutines waiting for c.
Broadcast can be called with or without locking.
Signal wakes up only 1 goroutine waiting for c.
Signal can be called with or without locking.
Wait() automatically releases
c.L and hangs the caller’s goroutine. execution resumes later, and
Wait() puts a lock on
c.L when it returns.
Wait() does not return unless it is woken up by Signal or Broadcast.
C.L is not locked when
Wait() first resumes, the caller usually does not assume that the condition is true when Wait returns.
Instead, the caller should call Wait in a loop. (Simply put, whenever you want to use a condition, you must add a lock.)
The following example is a better illustration of how Cond is used.
The execution results are as follows.
goroutine1 and goroutine2 enter Wait state, after main goroutine in 2s after the resource is satisfied, after issuing broadcast signal, resume from Wait and determine whether the condition has indeed been satisfied (sharedRsc is not empty), satisfied then consume the condition and unlock,
We make a modification to remove the 2s delay in the main goroutine.
The code will not be posted.
The execution result is as follows.
It is interesting to note that neither goroutine enters the Wait state.
The reason is that the main goroutine executes faster and has already acquired the lock before goroutine1/goroutine2 adds the lock and finishes modifying sharedRsc and signaling Broadcast.
When the child goroutine checks the condition before calling Wait, the condition is already satisfied, so there is no need to call Wait again.
What if we don’t do checksum in the subgoroutine?
We would get 1 deadlock.
The main goroutine (goroutine 1) executes first and stays in wg.Wait(), waiting for wg.Done() of the child goroutine; while the child goroutine (goroutine 6) calls cond.Wait directly without judging the condition.
Wait will release the lock and wait for the other goroutine to call Broadcast or Signal to notify it to resume execution, but there is no other way to resume. But the main goroutine has already called Broadcast and entered the wait state, so no goroutine will rescue the child goroutine that is still in cond. Deadlock.
Therefore, be sure to note that Broadcast must come after all the Wait (of course, it is possible to decide whether to go into Wait by conditional judgment).
A real example
Let’s take a look at the FIFO implemented in k8s using Cond, which How to handle the consumption of conditions.
Cond shares the FIFO’s lock, in Pop, it will add lock
f.lock.Lock() first, and before
f.cond.Wait(), it will check if
len(f.queue) is 0 to prevent 2 cases.
- as in example 3 above, the condition is satisfied, no need to wait
- The condition is satisfied when waking up, but other goroutines have gotten there first and blocked in the locking of
f.lock; when the lock is obtained and the locking is successful,
f.queuehas been consumed as empty, and direct access to
f.queuewill be accessed out of bounds.