Что такое mutex и rwmutex в Go и для чего они нужны
Что проверяют
Знаете ли вы разницу и понимаете ли, что вторая разновидность не всегда выгоднее.
Как отвечать
Оба защищают общее изменяемое состояние от одновременного доступа.
Обычный мьютекс пускает внутрь только одного, независимо от того, читает он или пишет.
Мьютекс с разделением чтения и записи различает два режима: читателей может быть сколько угодно одновременно, а писатель работает в одиночку и ждёт, пока все читатели выйдут.
type Cache struct {
mu sync.RWMutex
m map[string]int
}
func (c *Cache) Get(k string) int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.m[k]
}
func (c *Cache) Set(k string, v int) {
c.mu.Lock()
defer c.mu.Unlock()
c.m[k] = v
}Когда второй выгоден: чтений намного больше записей и критическая секция достаточно длинная. Тогда параллельное чтение действительно ускоряет работу.
Когда он проигрывает, и это стоит сказать: у него больше накладных расходов на сам захват. На очень короткой секции вроде чтения одного поля обычный мьютекс оказывается быстрее, потому что дополнительный учёт читателей стоит дороже, чем экономия от параллельности.
Что надо знать обязательно:
- оба не реентрантны. Повторный захват из той же горутины блокирует её навсегда. Классическая ловушка это метод, берущий блокировку и вызывающий другой метод того же объекта, который берёт её же;
- освобождение через отложенный вызов сразу после захвата, иначе ранний возврат оставит блокировку навсегда;
- структуру с мьютексом нельзя копировать: копия получит собственную независимую блокировку, и защита исчезнет. Именно поэтому методы объявляют на указателе, а встроенный статический анализатор специально ищет такие копирования;
- не делать под блокировкой ничего долгого: сетевых вызовов, обращений к диску, вызовов чужого кода.
Где спотыкаются
Берут разделяющий мьютекс всегда, считая его строго лучше.
Захватывают блокировку повторно из вложенного метода.
Копируют структуру с мьютексом.
Читают состояние без блокировки, защищая только запись.