Безопасно ли удалять выбранные ключи с карты Голанга в цикле диапазона?

Как удалить выбранные ключи с карты Голанга? Безопасно ли комбинировать delete() с диапазоном, как в коде ниже?

http://play.golang.org/p/u1vufvEjSw

package main

import "fmt"

type Info struct {
    value string
}

func main() {
    table := make(map[string]*Info)

    for i := 0; i < 10; i++ {
        str := fmt.Sprintf("%v", i)
        table[str] = &Info{str}
    }

    for key, value := range table {
        fmt.Printf("deleting %v=>%v\n", key, value.value)
        delete(table, key)
    }
}

Ответ 1

Это безопасно! Вы также можете найти аналогичный образец в Эффективный ход:

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

И спецификация языка:

Порядок итераций по картам не указан и не может быть одинаковым с одной итерации на следующую. Если записи карт, которые еще не были достигнуты, удалены во время итерации, соответствующие значения итераций не будут созданы. Если записи карты созданы во время итерации, эта запись может быть получена во время итерации или может быть пропущена. Выбор может отличаться для каждой созданной записи и от одной итерации к следующей. Если отображение равно нулю, число итераций равно 0.

Ответ 2

Ответ Себастьяна является точным, но я хотел знать, почему это было безопасно, поэтому я немного перекопал в исходный код карты. Похоже, что при вызове delete(k, v) он просто устанавливает флаг (а также меняет значение счетчика) вместо фактического удаления значения:

b->tophash[i] = Empty;

(Пустое является константой для значения 0)

То, что, по-видимому, на самом деле делает карта, это выделение определенного количества ведер в зависимости от размера карты, которое растет при выполнении вставок со скоростью 2^B (из этот исходный код):

byte    *buckets;     // array of 2^B Buckets. may be nil if count==0.

Таким образом, почти всегда выделено больше ковшей, чем вы используете, и когда вы делаете range над картой, он проверяет значение tophash каждого ведра в этом 2^B, чтобы увидеть, может ли он пропускать над ним.

Подводя итог, delete в пределах range является безопасным, поскольку данные технически все еще существуют, но когда он проверяет tophash, он видит, что он может просто пропустить его и не включать в него range которую вы выполняете. Исходный код даже включает в себя TODO:

 // TODO: consolidate buckets if they are mostly empty
 // can only consolidate if there are no live iterators at this size.

Это объясняет, почему использование функции delete(k,v) фактически не освобождает память, а просто удаляет ее из списка ведер, к которым вы имеете доступ. Если вы хотите освободить фактическую память, вам понадобится сделать всю карту недоступной, чтобы в нее входила сборка мусора. Вы можете сделать это, используя строку типа

map = nil

Ответ 3

Мне было интересно, может ли произойти утечка памяти. Поэтому я написал тестовую программу:

package main

import (
    log "github.com/Sirupsen/logrus"
    "os/signal"
    "os"
    "math/rand"
    "time"
)

func main() {
    log.Info("=== START ===")
    defer func() { log.Info("=== DONE ===") }()

    go func() {
        m := make(map[string]string)
        for {
            k := GenerateRandStr(1024)
            m[k] = GenerateRandStr(1024*1024)

            for k2, _ := range m {
                delete(m, k2)
                break
            }
        }
    }()

    osSignals := make(chan os.Signal, 1)
    signal.Notify(osSignals, os.Interrupt)
    for {
        select {
        case <-osSignals:
            log.Info("Recieved ^C command. Exit")
            return
        }
    }
}

func GenerateRandStr(n int) string {
    rand.Seed(time.Now().UnixNano())
    const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
    }
    return string(b)
}

Похоже, GC делает освобождает память. Так что все в порядке.