Mastering Go’s sync.Cond: Coordinating Goroutine Execution with Condition Variables
This article explains how Go's sync.Cond works, demonstrates its usage with practical code examples, analyzes its internal implementation, and shows a real‑world application in Kubernetes FIFO queues, helping developers control goroutine synchronization effectively.
Cond’s main purpose is to acquire a lock, then wait for a notification via wait(), allowing controlled release and frequency of the lock in concurrent environments.
Example
Here is a basic usage of Cond:
package main
import (
"fmt"
"sync"
"time"
)
var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)
func main() {
for i := 0; i < 40; i++ {
go func(x int) {
cond.L.Lock()
defer cond.L.Unlock()
cond.Wait()
fmt.Println(x)
time.Sleep(time.Second * 1)
}(i)
}
time.Sleep(time.Second * 1)
fmt.Println("Signal...")
cond.Signal()
time.Sleep(time.Second * 1)
cond.Signal()
time.Sleep(time.Second * 3)
cond.Broadcast()
fmt.Println("Broadcast...")
time.Sleep(time.Second * 60)
}Cond Structure
type Cond struct {
noCopy noCopy
L Locker
notify notifyList
checker copyChecker
}notifyList Structure
type notifyList struct {
wait uint32
notify uint32
lock uintptr
head unsafe.Pointer
tail unsafe.Pointer
}NewCond
func NewCond(l Locker) *Cond {
return &Cond{L: l}
}Wait
func (c *Cond) Wait() {
c.checker.check()
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
}Signal
func (c *Cond) Signal() {
c.checker.check()
runtime_notifyListNotifyOne(&c.notify)
}Broadcast
func (c *Cond) Broadcast() {
c.checker.check()
runtime_notifyListNotifyAll(&c.notify)
}In Kubernetes, Cond is used to implement a FIFO queue that coordinates condition consumption.
Real‑world FIFO Example
func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) {
f.lock.Lock()
defer f.lock.Unlock()
for {
if len(f.queue) == 0 {
// block until new item is enqueued or closed
f.cond.Wait()
}
if f.IsClosed() {
return nil, FIFOClosedError
}
// retrieve and remove the first element
id := f.queue[0]
f.queue = f.queue[1:]
// process the item
// ...
}
}
func NewFIFO(keyFunc KeyFunc) *FIFO {
f := &FIFO{
items: map[string]interface{}{},
queue: []string{},
keyFunc: keyFunc,
}
f.cond = sync.NewCond(&f.lock)
return f
}The FIFO shares the same lock with Cond; before calling f.cond.Wait() it checks whether the queue length is zero to avoid waiting when the condition is already satisfied or to prevent race conditions that could cause out‑of‑bounds access.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
