Мьютекс защищает состояние. Но иногда нужно ещё и дождаться его изменения. Писать цикл с паузой можно, а sync.Cond делает ожидание правильным и дешёвым.

Mutex и RWMutex

Mutex даёт взаимное исключение и видимость записей. RWMutex допускает параллельных читателей, но один писатель.

type Cache struct {
    mu    sync.RWMutex
    items map[string]string
}

func (c *Cache) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    value, ok := c.items[key]
    return value, ok
}

RWMutex не ускоряет автоматически. Он полезен, когда чтений много и они длительные. На маленьких критических секциях обычный Mutex проще и часто быстрее.

Правила:

  • defer Unlock ставят сразу после Lock;
  • структуру с мьютексом не копируют после первого использования;
  • длительные вызовы БД, HTTP и пользовательского кода не держат под блокировкой;
  • внутри RLock нельзя писать.

Зачем нужен sync.Cond

Допустим, воркер должен ждать появления заказа:

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

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

func (q *Queue) Push(item string) {
    q.mu.Lock()
    q.items = append(q.items, item)
    q.cond.Signal()
    q.mu.Unlock()
}

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

Wait на время снимает мьютекс и засыпает. После пробуждения мьютекс снова захватывается. Поэтому состояние проверяется в цикле: другой потребитель мог забрать элемент раньше, а Broadcast будит всех.

Signal и Broadcast

Signal будит одного ждущего, Broadcast всех. Выбирают по работе:

  • новый элемент может обработать один воркер - Signal;
  • изменилось общее состояние, которое интересно всем - Broadcast.

Пробуждение не гарантирует наличие элемента. Условие всегда повторяют.

Типичные ошибки

Первая - проверка условия без цикла. Вторая - Signal перед захватом мьютекса при записи: ждущий может проснуться, увидеть пустую очередь и снова заснуть. Третья - ожидание отмены без выхода из Wait. sync.Cond не поддерживает контекст: отмену реализуют через закрытие канала или дополнительный Broadcast после изменения флага.

Для каналов такая задача часто проще. Если потребитель один, канал сам становится очередью и синхронизацией. Cond полезен для составных условий и общего состояния.

Коротко

  • Мьютекс защищает состояние, Cond позволяет ждать его изменения.
  • Wait снимает мьютекс и возвращает его при пробуждении.
  • Условие проверяется в цикле, потому что пробуждение не доказывает его истинность.
  • Для очереди задач канал часто проще condition variable.