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

Ответ

Основное отличие sync.RWMutex от sync.Mutex заключается в том, что RWMutex предоставляет два типа блокировок: одну для записи (эксклюзивную) и одну для чтения (разделяемую).

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

Ключевые сценарии использования:


  1. Частое чтение, редкая запись: Классический случай. Если у вас есть конфигурация, кэш или другие данные, которые читаются постоянно, а изменяются редко. Эмпирическое правило — количество чтений превышает количество записей как минимум на порядок (в 10+ раз).



  2. Длительные операции чтения: Если операция чтения сама по себе занимает значительное время (например, обход большого среза или мапы), RWMutex позволит этим операциям выполняться параллельно, в то время как Mutex заставил бы их ждать друг друга.



  3. Высокая конкуренция за ресурс: Когда множество горутин одновременно пытаются получить доступ к данным для чтения.


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

package main

import (
    "sync"
    "time"
)

var (
    cache = make(map[string]string)
    rwMu  = sync.RWMutex{}
)

// Get позволяет множеству горутин читать одновременно
func Get(key string) string {
    rwMu.RLock() // Блокировка на чтение
    defer rwMu.RUnlock()
    // Имитация долгой операции чтения
    time.Sleep(10 * time.Millisecond)
    return cache[key]
}

// Set блокирует всех (и читателей, и писателей) для эксклюзивного доступа
func Set(key, value string) {
    rwMu.Lock() // Блокировка на запись
    defer rwMu.Unlock()
    cache[key] = value
}

Важные моменты и "подводные камни":

  • Голодание писателей (Writer Starvation): Если поток читателей непрерывен, горутина, ожидающая блокировки на запись, может никогда её не получить. В стандартной реализации sync.RWMutex в Go этот риск снижен, так как она отдает предпочтение ожидающему писателю перед новыми читателями, но о такой возможности стоит помнить.
  • Накладные расходы: RWMutex сложнее и медленнее, чем Mutex. Если критическая секция очень маленькая и быстрая, или если количество записей сопоставимо с количеством чтений, производительность с RWMutex может оказаться хуже, чем с обычным Mutex из-за дополнительных затрат на координацию.

Вывод: Выбор между Mutex и RWMutex — это компромисс. RWMutex эффективен для сценариев "много читателей, мало писателей", но в других случаях простой Mutex может быть более производительным и предсказуемым решением.