Конкурентная запись в map без синхронизации — одна из самых опасных ошибок в Go, приводящая к неопределённому поведению и паникам. Рассмотрим профессиональные методы защиты.
var (
data = make(map[string]int)
mu sync.Mutex
)
func safeWrite(key string, value int) {
mu.Lock()
defer mu.Unlock()
data[key] = value
}
Особенности:
defer
гарантирует разблокировку даже при паникеvar (
data = make(map[string]int)
rwMu sync.RWMutex
)
func safeRead(key string) (int, bool) {
rwMu.RLock()
defer rwMu.RUnlock()
val, ok := data[key]
return val, ok
}
var sm sync.Map
// Запись
sm.Store("key", 42)
// Чтение
if val, ok := sm.Load("key"); ok {
fmt.Println(val)
}
Когда использовать:
import "sync/atomic"
var mapPtr atomic.Pointer[map[string]int]
func init() {
m := make(map[string]int)
mapPtr.Store(&m)
}
func updateMap(key string, value int) {
newMap := make(map[string]int)
oldMap := *mapPtr.Load()
// Копируем старые данные
for k, v := range oldMap {
newMap[k] = v
}
// Добавляем новые
newMap[key] = value
// Атомарная замена
mapPtr.Store(&newMap)
}
const shards = 64
type ConcurrentMap struct {
shards []*MapShard
}
type MapShard struct {
data map[string]int
mu sync.RWMutex
}
func (cm *ConcurrentMap) Set(key string, value int) {
shard := fnv32(key) % shards
cm.shards[shard].mu.Lock()
cm.shards[shard].data[key] = value
cm.shards[shard].mu.Unlock()
}
type MapOperation struct {
key string
value int
action string // "set", "get", "delete"
resp chan interface{}
}
func mapManager(ops chan MapOperation) {
data := make(map[string]int)
for op := range ops {
switch op.action {
case "set":
data[op.key] = op.value
op.resp <- true
case "get":
op.resp <- data[op.key]
}
}
}
go run -race main.go
go test -race ./...
go vet
staticcheck
// НЕ ДЕЛАЙТЕ ТАК!
mu.RLock()
_, exists := data[key]
mu.RUnlock()
if !exists {
mu.Lock()
data[key] = value // Гонка данных!
mu.Unlock()
}
Глобальные переменные без защиты
Копирование sync-примитивов