Ответ
Оба подхода решают проблему конкурентного доступа к 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
Это специализированная структура данных, оптимизированная для двух конкретных случаев:
- Когда ключ записывается один раз, а затем читается много раз.
- Когда несколько горутин конкурентно работают с разными наборами ключей.
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
является узким местом в вашем приложении, и ваш сценарий использования соответствует одному из двух описанных выше.