When would you use sync.Cond, and why must Wait always be called in a loop?
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
- Q225What is the classic sync.WaitGroup bug, and what does wg.Go (Go 1.25) change?
- Q226Explain sync.Once semantics, including panics, and the OnceFunc / OnceValue / OnceValues helpers.
- Q228How does sync.Pool work, how does it interact with the GC, and what are the common misuse pitfalls?
- Q229When should you use sync.Map instead of a map guarded by a Mutex?
- Q230What are typed atomics (atomic.Int64, atomic.Pointer[T]) and why prefer them over atomic.AddInt64 / atomic.Value?
- Q231Implement a lock-free "store max" using compare-and-swap. What is the ABA problem, and does it affect Go?