Ответ
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 // для продолжения итерации
}) Ответ 18+ 🔞
Давай разберём эту штуку, которая называется sync.Map. Ну, типа, карта, но с приставкой sync, что намекает на какую-то синхронизацию. Ага, так и есть — это встроенная в Go потокобезопасная мапа, но, блядь, не для всех случаев жизни. Она как спецназ: крута в очень узких операциях, а в остальном — обычный солдат с мьютексом может оказаться лучше.
Что у неё внутри, ёпта? (Грубо говоря)
Представь, у неё две квартиры, простите, карты:
read: Карта только для чтения. На неё можно смотреть всем подряд без очереди и без драки. Указатель на неё атомарный.dirty: Грязная, блядь, карта. Вот тут уже бардак, мьютекс и все дела. Туда пишут всё новое. Потом, когда она наберётся, её "повышают" до статусаread.
Когда её впихивать в проект? (Когда она реально охуенна)
- Записал один раз — читай до посинения: Идеальный сценарий — кэш каких-нибудь конфигов, которые обновляются раз в год. Записали, а потом 10 тысяч горутин их читают. Тут она блестит, как ядрёна вошь.
- Разные ключи — разные горутины: Если одна горутина ковыряется с ключами
A, B, C, а другая — сX, Y, Z, и они редко пересекаются. Конфликтов минимум, жизнь хороша.
А когда это пиздопроебибна идея? (Лучше взять обычную map и sync.RWMutex)
- Частые перезаписи: Если ты как сумасшедший постоянно обновляешь значения по одним и тем же ключам, то вся эта магия с двумя картами превратится в адские накладные расходы. Обычный мьютекс будет проще и быстрее.
- Хочется типов, а не
interface{}:sync.Mapхранит всё какinterface{}, значит, каждый раз при чтении надо делать приведение типаval.(string). Статической проверки — ноль. С дженериками в Go 1.18 можно накодить свою типизированную карту, и будет красота. - Надо узнать размер: Захочешь узнать, сколько там элементов —
m.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 // вернёшь false — итерация остановится
})
Короче, sync.Map — инструмент не универсальный. Подозрение ебать чувствую, что её часто используют просто потому, что она есть в стандартной библиотеке и звучит умно. Но прежде чем её впилить, подумай: а твой случай — это "записал-читай" или "все пишут и читают в одну точку"? От этого зависит, будешь ты героем или тем самым чуваком, который оптимизировал не там, где надо.