Ответ
sync.Map — это встроенная в Go потокобезопасная структура данных (map), оптимизированная для очень специфичных сценариев конкурентного доступа.
Внутреннее устройство (упрощенно):
sync.Map работает без глобального мьютекса для операций чтения. Она использует две внутренние карты:
read: Атомарный указатель на read-only карту, доступ к которой для чтения не требует блокировок.dirty: Обычнаяmapс мьютексом для новых записей и обновлений. Периодическиdirtyкарта "продвигается" доread.
Когда стоит использовать sync.Map:
- Кэши "write-once, read-many": Когда ключ записывается один раз, а затем многократно читается множеством горутин. Например, кэш редко изменяемых данных.
- Дисперсный доступ: Когда разные горутины работают с разными наборами ключей, минимизируя конфликты при записи.
Когда sync.Map — не лучший выбор (и лучше map + sync.RWMutex):
- Частые записи и обновления: Если горутины часто перезаписывают значения по одним и тем же ключам, производительность
sync.Mapможет быть ниже из-за накладных расходов на управлениеdirtyкартой. - Нужна строгая типизация:
sync.Mapхранит ключи и значения какinterface{}, что требует приведения типов и лишает статической проверки. С появлением дженериков в Go 1.18 стало проще реализовать собственную типизированную конкурентную карту. - Нужно знать размер (
len): Уsync.Mapнет методаLen(), так как его вычисление было бы медленным и требовало бы блокировки. - Статические данные: Если карта инициализируется один раз и больше никогда не изменяется, обычная
mapбез всяких блокировок будет самым быстрым решением.
Пример использования:
var m sync.Map
// Запись (Store)
m.Store("user:1", "Alice")
// Чтение (Load)
val, ok := m.Load("user:1")
if ok {
fmt.Println(val.(string)) // Требуется приведение типа
}
// Итерация (Range)
m.Range(func(key, value interface{}) bool {
fmt.Printf("%s -> %sn", key, value)
return true // для продолжения итерации
})