Как сделать Map потокобезопасным при помощи Mutex в Go?

Ответ

Для создания потокобезопасной map в Go используйте sync.Mutex или, что чаще всего предпочтительнее для карт, sync.RWMutex (если нужны раздельные блокировки на чтение/запись). sync.RWMutex позволяет множеству горутин читать карту одновременно, но блокирует все операции при записи.

Пример использования sync.RWMutex:

package main

import (
    "fmt"
    "sync"
)

type SafeMap struct {
    sync.RWMutex
    data map[string]interface{}
}

// NewSafeMap создает новый экземпляр SafeMap
func NewSafeMap() *SafeMap {
    return &SafeMap{
        data: make(map[string]interface{}),
    }
}

// Get безопасно извлекает значение по ключу, возвращая значение и флаг наличия
func (sm *SafeMap) Get(key string) (interface{}, bool) {
    sm.RLock() // Блокировка для чтения
    defer sm.RUnlock() // Разблокировка после завершения чтения
    val, ok := sm.data[key]
    return val, ok
}

// Set безопасно устанавливает значение по ключу
func (sm *SafeMap) Set(key string, value interface{}) {
    sm.Lock() // Блокировка для записи
    defer sm.Unlock() // Разблокировка после завершения записи
    sm.data[key] = value
}

// Delete безопасно удаляет значение по ключу
func (sm *SafeMap) Delete(key string) {
    sm.Lock()
    defer sm.Unlock()
    delete(sm.data, key)
}

func main() {
    m := NewSafeMap()

    // Пример использования
    m.Set("name", "Alice")
    m.Set("age", 30)

    if val, ok := m.Get("name"); ok {
        fmt.Printf("Name: %vn", val)
    }

    if val, ok := m.Get("age"); ok {
        fmt.Printf("Age: %vn", val)
    }

    if _, ok := m.Get("city"); !ok {
        fmt.Println("City not found (as expected).")
    }

    m.Delete("age")
    if _, ok := m.Get("age"); !ok {
        fmt.Println("Age deleted.")
    }
}

Ключевые моменты:

  1. Встраивание sync.RWMutex: sync.RWMutex встраивается в структуру SafeMap. Это позволяет напрямую вызывать методы мьютекса (Lock, Unlock, RLock, RUnlock) на экземпляре SafeMap.
  2. Защита всех операций: Все операции, которые модифицируют или читают map (Get, Set, Delete), должны быть защищены мьютексом.
  3. defer для гарантированного разблокирования: Использование defer гарантирует, что мьютекс будет разблокирован (Unlock() или RUnlock()) после завершения функции, даже если произойдет паника.
  4. RWMutex для параллельных чтений: sync.RWMutex является более эффективным выбором, чем простой sync.Mutex, для карт, так как он позволяет множеству горутин читать данные одновременно (RLock()), но при этом обеспечивает эксклюзивный доступ для операций записи (Lock()).
  5. Альтернатива sync.Map: Для некоторых специфических сценариев (например, когда преобладают чтения, а записи редки и не конфликтуют по ключам) можно использовать встроенный sync.Map, который оптимизирован для таких нагрузок и не требует явного управления мьютексами.