При одновременной записи в map из нескольких горутин возникают следующие проблемы:
fatal error: concurrent map writes
m := make(map[int]int)
for i := 0; i < 100; i++ {
go func() {
m[i] = i // Конкурентная запись
}()
}
Map не потокобезопасны по дизайну:
Реализация хеш-таблицы:
Лучший выбор для большинства случаев
var (
m = make(map[int]int)
mux sync.Mutex
)
// Запись
mux.Lock()
m[key] = value
mux.Unlock()
// Чтение
mux.Lock()
v := m[key]
mux.Unlock()
Плюсы:
Минусы:
Оптимизация для частых чтений
var (
m = make(map[int]int)
rwMux sync.RWMutex
)
// Запись
rwMux.Lock()
m[key] = value
rwMux.Unlock()
// Чтение
rwMux.RLock()
v := m[key]
rwMux.RUnlock()
Специализированная потокобезопасная map
var sm sync.Map
// Запись
sm.Store(key, value)
// Чтение
if v, ok := sm.Load(key); ok {
// использование v
}
Плюсы:
Минусы:
Для уменьшения конкуренции
const shards = 32
type ConcurrentMap []*MapShard
type MapShard struct {
items map[int]int
sync.RWMutex
}
func New() ConcurrentMap {
cm := make(ConcurrentMap, shards)
for i := range cm {
cm[i] = &MapShard{items: make(map[int]int)}
}
return cm
}
func (cm ConcurrentMap) Set(key int, value int) {
shard := key % shards
cm[shard].Lock()
cm[shard].items[key] = value
cm[shard].Unlock()
}
sync.Mutex:
sync.RWMutex:
sync.Map:
Шардинг:
func main() {
var mux sync.RWMutex
m := make(map[string]int)
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(i int) {
key := fmt.Sprintf("key%d", i)
mux.Lock()
m[key] = i
mux.Unlock()
wg.Done()
}(i)
}
wg.Wait()
}