В чем разница между стандартной `map` и `sync.Map` в Go?

Ответ

Основное различие заключается в потокобезопасности и сценариях использования.

map (стандартная мапа)

Это встроенный тип данных в Go. Он не является потокобезопасным. Попытка одновременной записи или чтения и записи из разных горутин без дополнительной синхронизации (например, мьютекса) приведёт к панике fatal error: concurrent map read and map write.

  • Преимущества: Максимальная производительность в однопоточных сценариях.
  • Недостатки: Требует ручной синхронизации для конкурентного доступа.

sync.Map

Это специальная реализация мапы из пакета sync, которая является потокобезопасной. Однако она спроектирована и оптимизирована для очень специфичного сценария использования:

  1. Ключи, однажды записанные в мапу, в основном только читаются и крайне редко изменяются или удаляются.
  2. Разные горутины преимущественно работают с разными наборами ключей, что снижает внутреннюю конкуренцию.
  • Преимущества: Потокобезопасность "из коробки". Высокая производительность в сценариях "записал один раз, читай много раз" (например, кэши).
  • Недостатки: Медленнее, чем map с мьютексом, в сценариях со смешанной или частой записью. Более сложный API (Store, Load, LoadOrStore, Delete).
package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map

    // Запись значения (потокобезопасно)
    m.Store("city", "Moscow")

    // Чтение значения (потокобезопасно)
    // `Load` возвращает `interface{}` и `bool` (был ли ключ найден)
    value, ok := m.Load("city")
    if ok {
        fmt.Println(value.(string)) // Вывод: Moscow
    }

    // Удаление
    m.Delete("city")
}

Альтернатива: map + sync.RWMutex

Для большинства общих задач, где требуется потокобезопасная мапа с частыми записями и чтениями, лучшим выбором является стандартная map, защищённая мьютексом sync.RWMutex. Это даёт более предсказуемую производительность и является идиоматичным решением в Go.

Когда что использовать:

  • map: Если доступ к мапе осуществляется только из одной горутины.
  • map + sync.RWMutex: Для большинства общих случаев, когда нужна потокобезопасная мапа (смешанные чтение/запись).
  • sync.Map: В нишевых сценариях с высокой конкуренцией, где данные преимущественно читаются (например, кэш метаданных, который редко обновляется).