Ответ
В Go встроенный тип map
не является потокобезопасным. Для безопасного доступа к map
в конкурентной среде используйте sync.RWMutex
или sync.Map
.
1. Использование sync.RWMutex
(Read-Write Mutex):
sync.RWMutex
предоставляет раздельные блокировки для чтения (RLock()
) и записи (Lock()
). Это позволяет множеству горутин читать map
одновременно, но блокирует все операции (чтение и запись) при выполнении записи.
package main
import (
"fmt"
"sync"
"time"
)
type SafeMap struct {
sync.RWMutex
data map[string]interface{}
}
// NewSafeMap создает новый потокобезопасный Map
func NewSafeMap() *SafeMap {
return &SafeMap{
data: make(map[string]interface{}),
}
}
// Get безопасно извлекает значение по ключу
func (m *SafeMap) Get(key string) (interface{}, bool) {
m.RLock() // Блокировка для чтения
defer m.RUnlock() // Разблокировка после завершения чтения
val, ok := m.data[key]
return val, ok
}
// Set безопасно устанавливает значение по ключу
func (m *SafeMap) Set(key string, value interface{}) {
m.Lock() // Блокировка для записи
defer m.Unlock() // Разблокировка после завершения записи
m.data[key] = value
}
// Delete безопасно удаляет значение по ключу
func (m *SafeMap) Delete(key string) {
m.Lock()
defer m.Unlock()
delete(m.data, key)
}
// Len возвращает количество элементов в Map
func (m *SafeMap) Len() int {
m.RLock()
defer m.RUnlock()
return len(m.data)
}
func main() {
safeMap := NewSafeMap()
var wg sync.WaitGroup
// Запись данных из нескольких горутин
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
key := fmt.Sprintf("key%d", id)
value := fmt.Sprintf("value%d", id)
safeMap.Set(key, value)
fmt.Printf("Goroutine %d: Set %s = %sn", id, key, value)
}(i)
}
// Чтение данных из нескольких горутин
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
time.Sleep(10 * time.Millisecond) // Даем время для записи
key := fmt.Sprintf("key%d", id*2) // Читаем некоторые ключи
val, ok := safeMap.Get(key)
if ok {
fmt.Printf("Goroutine %d: Get %s = %vn", id, key, val)
} else {
fmt.Printf("Goroutine %d: Key %s not foundn", id, key)
}
}(i)
}
wg.Wait()
fmt.Printf("nFinal map size: %dn", safeMap.Len())
val, ok := safeMap.Get("key5")
if ok {
fmt.Printf("Final check: key5 = %vn", val)
}
}
2. Использование sync.Map
:
sync.Map
— это специализированная реализация потокобезопасной карты, оптимизированная для следующих сценариев:
- Когда ключи стабильны (редко меняются):
sync.Map
эффективна, когда набор ключей относительно статичен, и большинство операций — это чтения. - Много конкурентных чтений: Она обеспечивает высокую производительность при большом количестве одновременных операций чтения.
- Разные горутины пишут в разные ключи: Если разные горутины обычно записывают в разные части карты,
sync.Map
может быть быстрее, чемRWMutex
, так как она минимизирует конфликты.
sync.Map
имеет методы Load
, Store
, LoadOrStore
, Delete
и Range
.
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// Store (запись)
m.Store("name", "Alice")
m.Store("age", 30)
// Load (чтение)
if val, ok := m.Load("name"); ok {
fmt.Printf("Name: %vn", val)
}
// LoadOrStore (загрузить или сохранить, если отсутствует)
// Если 'city' нет, сохранит 'New York' и вернет 'New York', true
// Если 'city' есть, вернет существующее значение, false
actual, loaded := m.LoadOrStore("city", "New York")
fmt.Printf("City: %v, Loaded existing: %tn", actual, loaded)
actual, loaded = m.LoadOrStore("age", 31) // 'age' уже есть
fmt.Printf("Age: %v, Loaded existing: %tn", actual, loaded)
// Delete (удаление)
m.Delete("age")
if _, ok := m.Load("age"); !ok {
fmt.Println("Age deleted.")
}
// Range (итерация по элементам)
fmt.Println("nIterating over map:")
m.Range(func(key, value interface{}) bool {
fmt.Printf("Key: %v, Value: %vn", key, value)
return true // Возвращаем true для продолжения итерации
})
}
Выбор между sync.RWMutex
и sync.Map
:
- Используйте
sync.RWMutex
для общего случая, когда вам нужен полный контроль над структурой данных и когда операции записи относительно часты или конфликтуют. - Используйте
sync.Map
для специфических сценариев, где преобладают чтения, ключи стабильны, и вы хотите избежать накладных расходов на блокировки для каждого чтения.