Ответ
В Go для потокобезопасной работы с map
в конкурентной среде можно использовать несколько подходов. Наиболее распространенные:
-
sync.Map
(специализированный случай):sync.Map
предоставляет потокобезопасную реализацию map, оптимизированную для случаев, когда записи происходят редко, а чтения — часто, или когда несколько горутин записывают уникальные ключи.Основные методы:
Store(key, value)
— сохраняет пару ключ-значение.Load(key)
— возвращает значение по ключу и булевый флаг наличия (value, ok
).LoadOrStore(key, value)
— загружает значение по ключу, если оно существует; иначе сохраняет новое значение. Возвращает фактическое значение и булевый флаг, указывающий, было ли значение загружено (actual, loaded
).Delete(key)
— удаляет ключ.Range(func(key, value) bool)
— итерирует по map. Функция-колбэк должна возвращатьtrue
для продолжения итерации,false
для остановки.
Пример:
package main import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("key1", "value1") val, ok := m.Load("key1") if ok { fmt.Printf("Загружено: %vn", val) // Загружено: value1 } actual, loaded := m.LoadOrStore("key1", "newValue") fmt.Printf("LoadOrStore (существующий): actual=%v, loaded=%tn", actual, loaded) // actual=value1, loaded=true actual, loaded = m.LoadOrStore("key2", "value2") fmt.Printf("LoadOrStore (новый): actual=%v, loaded=%tn", actual, loaded) // actual=value2, loaded=false m.Range(func(k, v interface{}) bool { fmt.Printf("Ключ: %v, Значение: %vn", k, v) return true }) m.Delete("key1") _, ok = m.Load("key1") fmt.Printf("Key1 существует после удаления: %tn", ok) // Key1 существует после удаления: false }
Когда использовать
sync.Map
:- Когда есть много горутин, читающих данные, и относительно мало горутин, записывающих данные.
- Когда набор ключей стабилен, но значения могут обновляться.
- Когда ключи добавляются только один раз (например, кэш).
- Избегайте
sync.Map
для частых обновлений или когда большинство операций — это записи/удаления, так как в этих случаях она может быть медленнее, чем обычнаяmap
с мьютексом.
-
sync.Mutex
илиsync.RWMutex
с обычнойmap
(наиболее общий случай): Это наиболее гибкий и часто используемый подход для синхронизацииmap
.sync.Mutex
: Простая блокировка, которая позволяет только одной горутине одновременно читать или записывать вmap
.sync.RWMutex
: Блокировка для чтения/записи. Позволяет множеству горутин читать одновременно, но только одной горутине записывать (и блокирует все чтения во время записи).
Пример с
sync.RWMutex
:package main import ( "fmt" "sync" ) type SafeMap struct { m map[string]string mu sync.RWMutex } func NewSafeMap() *SafeMap { return &SafeMap{m: make(map[string]string)} } func (sm *SafeMap) Store(key, value string) { sm.mu.Lock() // Блокируем для записи defer sm.mu.Unlock() sm.m[key] = value } func (sm *SafeMap) Load(key string) (string, bool) { sm.mu.RLock() // Блокируем для чтения defer sm.mu.RUnlock() val, ok := sm.m[key] return val, ok } func main() { sm := NewSafeMap() sm.Store("city", "New York") val, ok := sm.Load("city") if ok { fmt.Printf("Город: %sn", val) } }
Когда использовать
sync.Mutex
/sync.RWMutex
:- Это подход по умолчанию, когда требуется потокобезопасная
map
. RWMutex
предпочтителен, если чтений значительно больше, чем записей.Mutex
проще, если операции чтения и записи примерно равны по частоте или если операции очень короткие.
Выбор между sync.Map
и sync.Mutex
/sync.RWMutex
зависит от конкретного паттерна доступа к данным в вашем приложении.