Ответ
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 // для продолжения итерации
})