В чем разница между `map` с `sync.RWMutex` и `sync.Map` в Go?

Ответ

Оба подхода решают проблему конкурентного доступа к map, но предназначены для разных сценариев.

map + sync.RWMutex

Это стандартный и наиболее гибкий способ защиты map в Go. RWMutex позволяет множеству горутин одновременно читать данные, но запись требует эксклюзивной блокировки.

var (
    m  = make(map[string]int)
    mu sync.RWMutex
)

// Запись
mu.Lock()
m["key"] = 42
mu.Unlock()

// Чтение
mu.RLock()
_ = m["key"]
mu.RUnlock()

Плюсы:

  • Типобезопасность: Ключи и значения имеют конкретные типы.
  • Гибкость: Позволяет выполнять сложные атомарные операции под одной блокировкой (например, прочитать, изменить и записать значение).
  • Простота: Концептуально прост для понимания.

Минусы:

  • При высокой конкуренции за одни и те же данные мьютекс может стать "бутылочным горлышком".

sync.Map

Это специализированная структура данных, оптимизированная для двух конкретных случаев:

  1. Когда ключ записывается один раз, а затем читается много раз.
  2. Когда несколько горутин конкурентно работают с разными наборами ключей.

sync.Map достигает производительности за счет более сложного внутреннего устройства, которое минимизирует конфликты блокировок.

var sm sync.Map

// Запись
sm.Store("key", 42)

// Чтение
val, ok := sm.Load("key")
if ok {
    // val имеет тип interface{}, нужна проверка типа
    _ = val.(int)
}

Плюсы:

  • Высокая производительность в целевых сценариях с высокой конкуренцией.

Минусы:

  • Нет типобезопасности: Хранит interface{}, что требует проверок типа (type assertion) и может привести к ошибкам в рантайме.
  • Ограниченный API: Нет методов для получения размера, итерации по всем элементам менее удобны (Range).
  • Может быть медленнее, чем map + RWMutex при низкой конкуренции из-за своих накладных расходов.

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

  • map + RWMutex: Используйте по умолчанию. Это ваш основной выбор для защиты map.
  • sync.Map: Используйте только после профилирования, если вы обнаружили, что RWMutex является узким местом в вашем приложении, и ваш сценарий использования соответствует одному из двух описанных выше.