map в Go не потокобезопасны и требуют защиты через мьютексы при конкурентном доступе. В отличие от них, sync.Map обеспечивает безопасность при конкурентном доступе без необходимости явно использовать мьютексы.
Основные методы sync.Map
Delete(key any): удаляет элемент по ключу;
Load(key any) (value any, ok bool): извлекает значение по ключу;
LoadAndDelete(key any) (value any, loaded bool): извлекает и одновременно удаляет элемент;
LoadOrStore(key, value any) (actual any, loaded bool): извлекает значение или сохраняет новое, если ключ отсутствует;
Range(f func(key, value any) bool): выполняет функцию для каждого элемента мапы;
Store(key, value any): сохраняет пару ключ-значение.
cache contention
В коде sync.RWMutex можно увидеть, что при блокировке на чтение, каждая горутина атомарно обновляет счетчик readerCount. Когда ядро процессора обновляет счётчик, оно сбрасывает кеш для этого адреса в памяти для всех остальных ядер и объявляет, что владеет актуальным значением для адреса. Следующее ядро, прежде чем обновить счётчик, сначала вычитывает это значение из кеша другого ядра.
Передача между L2 кешем занимает ~ 40 нс. Когда много ядер одновременно пытаются обновить счётчик, то каждое становится в очередь и ждёт инвалидацию и вычитывание из кеша. Операция, которая должна укладываться в константное время внезапно становится O(N) по количеству ядер. Это и есть cache contention.
Для этой конкретной проблемы, когда у нас много ядер и система высоконагружена, можно использовать sync.Map вместо стандартной map с RWMutex. В остальных случаях sync.Map не нужен.
sync.Map в борьбе с Cache Contention
sync.Map оптимизирован для использования при частых чтениях и редких записях, что является обычной ситуацией в кэш-системах.
метод LoadOrStore позволяет избежать дополнительной блокировки при проверке наличия ключа и его сохранении. Если ключ уже существует, метод вернет его значение, в противном случае сохранит новое значение без риска конкурентного доступа
Рассмотрим пример частотной мапы, которая подсчитывает, сколько раз каждый ключ был запрошен:
Метод LoadOrStore обеспечивает добавление нового ключа или возврат существующего без необходимости дополнительной блокировки, что снижает риск возникновения cache contention.