Ответ
sync.Map
— это встроенный в Go тип данных, представляющий собой потокобезопасную (конкурентную) map. Он спроектирован и оптимизирован для конкретных сценариев, а не для общей замены map + sync.RWMutex
.
Основные сценарии использования:
- Кэш "записал один раз, читай много": Когда ключ записывается в map один раз, а затем многократно читается множеством горутин. Например, кэш метаданных или конфигурации.
- Раздельные наборы ключей: Когда разные горутины в основном работают с непересекающимися наборами ключей, что минимизирует конфликты при записи.
Как это работает под капотом?
sync.Map
использует две внутренние map: read
(только для чтения) и dirty
(для записи).
- Чтение существующих ключей происходит из
read
без блокировок, что делает его очень быстрым. - Запись, обновление или чтение нового ключа требует блокировки и работы с
dirty
map. Периодически данные изdirty
переносятся вread
.
Преимущества перед map + sync.RWMutex
:
- Отсутствие глобальной блокировки на чтение для уже существующих ключей, что значительно повышает производительность в read-mostly сценариях.
Когда НЕ стоит использовать sync.Map
:
- Для частых операций записи и удаления, особенно одних и тех же ключей. Это вызовет постоянную блокировку
dirty
map. - Если map инициализируется один раз и больше никогда не изменяется (достаточно обычной
map
). - Если требуется перебирать все элементы map. Метод
Range
менее удобен и производителен, чемfor ... range
для обычной map.
Пример:
var m sync.Map
// Запись значения. Потокобезопасно.
m.Store("user:1", "John Doe")
// Чтение значения. Потокобезопасно.
val, ok := m.Load("user:1")
if ok {
fmt.Println(val) // "John Doe"
}
// Запись и чтение в одной операции
actual, loaded := m.LoadOrStore("user:2", "Jane Doe")
// Удаление
m.Delete("user:1")