Go: что происходит, если изменять map во время обхода?
В гошке можно изменять map прямо во время range. Вот так вот, да! Без предупреждения, регистраций и смс.
Но у этого поведения есть несколько нюансов.
Например, удалять элементы во время обхода можно: users := map[int]string{ 1: "Кароси", 2: "Свежемороженый минтай", 3: "Ада Вонг", } for id, name := range users { if id == 2 { delete(users, id) } fmt.Println(id, name) }
Это допустимо. Удалённый элемент больше не будет доступен в map. Но есть и другой интересный момент.
Открываем ящик пандоры.
А если добавлять элементы?
users := map[int]string{ 1: "Кароси", 2: "Свежемороженый минтай", } for id, name := range users { fmt.Println(id, name) if id == 1 { users[3] = "Ада Вонг" } }
Появится ли новый элемент во время текущего обхода?
Возможно, да, а возможно, нет. Спецификация Go прямо говорит: если во время итерации добавить новый элемент, он может быть выдан при обходе, а может быть пропущен.
Поэтому строить логику на том, что новый элемент обязательно попадёт в текущий range, нельзя.
А что с порядком элементов?
вот так вот: for id, name := range users { fmt.Println(id, name) }
не гарантирует определённый порядок.
При разных запусках порядок может отличаться.
Если нужен предсказуемый результат, можно сначала собрать ключи: ids := make([]int, 0, len(users)) for id := range users { ids = append(ids, id) } slices.Sort(ids) for _, id := range ids { fmt.Println(id, users[id]) }
Теперь порядок будет определяться сортировкой ключей.
Важное отличие: конкурентный доступ
Изменять map во время обычного range в одной goroutine можно с учётом описанных правил.
Но если одна goroutine читает map, а другая одновременно изменяет её без синхронизации, это уже другая ситуация.
Такой код небезопасен: go func() { users[4] = "Коулун" }() for id, name := range users { fmt.Println(id, name) }
Для конкурентного доступа нужна синхронизация, например sync.Mutex, либо подходящая для задачи sync.Map.
Что стоит запомнить?
Удалять элементы во время range можно.
Добавленные элементы могут попасть в текущий обход, а могут и не попасть. Порядок обхода map не гарантирован.
Конкурентное чтение и изменение без синхронизации небезопасно.
range по map не создаёт снимок её содержимого.
Поэтому если нужен стабильный набор элементов для обработки, лучше сначала получить отдельный список ключей, а уже потом работать с ним.