Go

When would you use sync.Cond, and why must Wait always be called in a loop?

Question 227HardGo 1.22 to 1.25

sync.Cond lets goroutines wait for a condition on shared state that a mutex protects.

Wait does three things: it atomically unlocks c.L, suspends the goroutine, and relocks c.L before returning. Because another goroutine can take the lock and change the state between the wake-up and the relock, the condition may be false again when Wait returns. That is why you always re-check it in a for loop.

type Queue[T any] struct {
	mu    sync.Mutex
	cond  *sync.Cond
	items []T
}

func NewQueue[T any]() *Queue[T] {
	q := &Queue[T]{}
	q.cond = sync.NewCond(&q.mu)
	return q
}

func (q *Queue[T]) Put(v T) {
	q.mu.Lock()
	q.items = append(q.items, v)
	q.mu.Unlock()
	q.cond.Signal() // wake one waiter
}

func (q *Queue[T]) Get() T {
	q.mu.Lock()
	defer q.mu.Unlock()
	for len(q.items) == 0 {
		q.cond.Wait()
	}
	v := q.items[0]
	q.items = q.items[1:]
	return v
}

Signal wakes one waiter. Broadcast wakes all of them; use it when the state change could satisfy several waiters, such as a "closed" flag.

Cond's weakness: it cannot be combined with select or a context, so you cannot wait with a timeout. That is why many Go teams prefer channels, for example closing a channel as a broadcast. Cond still wins when many goroutines repeatedly wait on complex state, which is why parts of the standard library, such as the HTTP/2 flow-control pipe in net/http, still use it.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions