Как узнать количество ключей в map и sync.Map в Go
Что проверяют
Знаете ли вы, что у sync.Map нет метода Len, и понимаете ли, почему. Следом обычно спрашивают, когда sync.Map вообще стоит брать вместо обычной map с мьютексом.
Как отвечать
- обычная
map:len(m)работает за O(1), счётчик хранится внутри мапы. Уnil-мапыlenвернёт 0 без паники. Если к мапе ходят несколько горутин,lenчитают под тем жеsync.RWMutex, что и запись, иначе это гонка данных, её покажетgo test -race; sync.Map: метода для размера нет, считать приходится обходом.
n := 0
m.Range(func(_, _ any) bool { n++; return true })Это O(n), и результат не снимок. Range не замораживает мапу, параллельные Store и Delete могут попасть в обход, а могут и не попасть.
Почему нет Len: внутреннее устройство менялось (в Go 1.24 sync.Map перевели на конкурентное хеш-дерево), но смысл прежний. Точный счётчик пришлось бы атомарно обновлять на каждой операции, а структура заточена под конкурентное чтение без общей точки синхронизации.
Если размер нужен часто, рядом держат atomic.Int64. Нюанс: Store не сообщает, был ли ключ, поэтому для счётчика используют LoadOrStore и LoadAndDelete, которые возвращают флаг loaded. Иначе повторная запись того же ключа увеличит счётчик.
Когда брать sync.Map: документация называет два случая. Ключ записывается один раз и потом много читается, как в кеше, который только растёт. Или горутины работают с непересекающимися наборами ключей. В остальных случаях map с sync.RWMutex проще, типобезопаснее (у sync.Map ключи и значения типа any) и часто быстрее.
Где спотыкаются
Пишут len(m) для sync.Map и получают ошибку компиляции, или выдумывают метод Len().
Используют результат подсчёта через Range как точный. Между подсчётом и следующим действием размер уже изменился, поэтому логику вида «если меньше ста, добавить» без внешней блокировки так не построить.
Читают обычную мапу без блокировки, считая чтение безопасным. Одновременные чтение по ключу и запись рантайм обрывает фатальной ошибкой concurrent map read and map write, а len без мьютекса остаётся гонкой, даже если сейчас не падает.