В каких случаях в Go следует предпочесть `sync.RWMutex` обычному `sync.Mutex`?

Ответ

RWMutex (мьютекс чтения-записи) стоит использовать в сценариях, где количество операций чтения значительно превышает количество операций записи. Это называется "read-heavy" нагрузкой.

Основной принцип работы:

  • RLock() (блокировка на чтение) может быть захвачена множеством горутин одновременно. Пока есть хотя бы один читатель, никто не может получить блокировку на запись.
  • Lock() (блокировка на запись) является эксклюзивной. Когда горутина захватывает Lock(), ни одна другая горутина не может получить ни RLock(), ни Lock().

Это позволяет множеству читателей безопасно работать с данными параллельно, что значительно повышает производительность.

Пример: кэш в памяти

var (
    cache = make(map[string]string)
    rw    sync.RWMutex
)

// Множество горутин могут вызывать Get одновременно
func Get(key string) string {
    rw.RLock() // Блокировка на чтение
    defer rw.RUnlock()
    return cache[key]
}

// Только одна горутина может вызывать Set в один момент времени
func Set(key, value string) {
    rw.Lock() // Эксклюзивная блокировка на запись
    defer rw.Unlock()
    cache[key] = value
}

Когда использовать Mutex?

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