Ответ
Стандартная map
в Go не является потокобезопасной. Оптимизация производительности зависит от сценария использования.
1. Предварительное выделение памяти (Pre-allocation)
Если вы заранее знаете примерное количество элементов, выделите память при создании map
. Это позволит избежать многократных реаллокаций и копирования данных при росте map
.
// Выделяем память под 1000 элементов
m := make(map[string]int, 1000)
2. Использование sync.RWMutex
для конкурентного доступа
Это стандартный и наиболее гибкий способ сделать map
потокобезопасной. RWMutex
позволяет множественные одновременные чтения, что идеально для сценариев, где чтений гораздо больше, чем записей.
var mu sync.RWMutex
m := make(map[string]int)
// Запись
mu.Lock()
m["key"] = 42
mu.Unlock()
// Чтение
mu.RLock()
value := m["key"]
mu.RUnlock()
3. Использование sync.Map
sync.Map
— это встроенный потокобезопасный тип, оптимизированный для сценариев, где ключ после записи редко изменяется или удаляется, а количество чтений велико. Внутренне он использует lock-free механизмы для чтения существующих ключей, что делает его очень быстрым в таких условиях.
var sm sync.Map
// Запись
sm.Store("key", 42)
// Чтение
value, ok := sm.Load("key")
Важно: Для смешанных нагрузок (частые записи и чтения) sync.Map
может быть медленнее, чем map
с RWMutex
из-за своей внутренней сложности.
4. Шардирование (Sharding)
Для сценариев с очень высокой конкуренцией (high contention), когда даже RWMutex
становится узким местом, map
можно разделить на несколько частей (шардов), каждая со своим мьютексом. Ключ хешируется для определения, в каком шарде его искать.
const shardCount = 32
type ShardedMap [shardCount]struct {
sync.RWMutex
items map[string]interface{}
}
// Для доступа к данным нужно сначала выбрать шард по хешу ключа,
// а затем заблокировать только мьютекс этого шарда.
Вывод: Выбор зависит от задачи. Для простых случаев — map
с RWMutex
. Для read-heavy сценариев — sync.Map
. Для экстремальной производительности — шардирование.