Мьютекс защищает состояние. Но иногда нужно ещё и дождаться его изменения. Писать цикл с паузой можно, а 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.