Как организовать мьютексы для многопоточной работы с данными по разным пользователям без блокировки всего стореджа
Что проверяют
Умеете ли вы сузить область блокировки и при этом не получить гонку на самой карте мьютексов. Следом обычно спрашивают, как удалять мьютексы ушедших пользователей и чем здесь поможет или не поможет sync.Map.
Как отвечать
Рабочих подходов два, их стоит сравнить.
- шардированная карта (lock striping): фиксированный массив из N шардов, в каждом своя
mapиsync.RWMutex. Шард выбирается хэшем ключа (hash/maphashилиhash/fnv) по модулю N. Код простой, память под мьютексы не растёт, пользователи из разных шардов не мешают друг другу. Цена: два пользователя из одного шарда всё же ждут друг друга, поэтому N берут с запасом, например 256; - мьютекс на ключ: карта
map[string]*entry, гдеentryсодержит свойsync.Mutexи данные пользователя. Саму карту защищает общий мьютекс, но держат его только на время поиска или создания записи, дальше блокируется мьютекс записи. То же можно сделать черезsync.MapиLoadOrStore, чтобы две горутины не создали две записи для одного ключа.
У второго варианта есть ловушка: записи копятся вечно. Для удаления нужен счётчик ссылок под общим мьютексом, иначе вы удалите мьютекс, который прямо сейчас ждёт другая горутина.
Если чтений намного больше, чем записей, RWMutex в шарде даёт выигрыш, иначе обычный Mutex дешевле. Альтернатива без мьютексов: по горутине-владельцу на шард и обмен через каналы.
Проверяется всё через go test -race, а go vet поймает копирование структуры с мьютексом внутри.
Где спотыкаются
Читают карту мьютексов без блокировки, как в store.userMu[name].Lock(), пока другая горутина добавляет пользователя. На одновременные чтение и запись в map рантайм обычно отвечает падением с concurrent map read and map write.
Отдают наружу указатель на данные пользователя. Мьютекс защищает только код внутри методов, а вызывающий меняет поля уже без блокировки.
Считают sync.Map картой с блокировкой по ключу. Она защищает только саму карту, а изменение значения внутри по-прежнему требует своего мьютекса или атомиков.