Czy usuwanie wybranych klawiszy z mapy w pętli zasięgu jest bezpieczne?

Jak usunąć wybrane klawisze z mapy? Czy można bezpiecznie łączyć delete() Z range, jak w poniższym kodzie?

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)
    }
}

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

Author: Tim Cooper, 2014-04-23

4 answers

To bezpieczne! Podobną próbkę znajdziesz również w Effective Go :

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

I specyfikacja języka:

Kolejność iteracji na mapach nie jest określona i nie ma gwarancji, że będzie taka sama z jednej iteracji na drugą. Jeśli pozycje map, które nie zostały jeszcze osiągnięte, zostaną usunięte podczas iteracji , odpowiednie wartości iteracji nie zostaną wygenerowane. Jeśli podczas iteracji zostaną utworzone wpisy na mapie, wpis ten może być wytworzony podczas iteracji lub może zostać pominięty. Wybór może się różnić dla każdego utworzonego wpisu i od jednej iteracji do następnej. Jeśli mapa jest zerowa, liczba iteracji wynosi 0.

 115
Author: Sebastian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-18 16:35:23

Odpowiedź Sebastiana jest dokładna, ale chciałem wiedzieć dlaczego było bezpiecznie, więc trochę poszperałem w kod źródłowy Mapy. Wygląda na to, że podczas wywołania delete(k, v), w zasadzie ustawia flagę (a także zmienia wartość count) zamiast faktycznie usuwać wartość:

b->tophash[i] = Empty;

(Empty jest stałą dla wartości 0)

To, co wydaje się faktycznie robić, to przydzielanie określonej liczby wiader w zależności od wielkości mapy, która rośnie jako wstawiania wykonujesz w tempie 2^B (z tego kodu źródłowego):

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

Więc prawie zawsze jest więcej wiadrów przydzielonych niż używasz, a kiedy robisz range nad mapą, sprawdza ona tophash wartość każdego wiadra w tym 2^B, aby zobaczyć, czy może go przeskoczyć.

Podsumowując, delete wewnątrz range jest bezpieczny, ponieważ dane są technicznie nadal tam, ale kiedy sprawdza tophash widzi, że może po prostu pominąć go i nie zawierać go w cokolwiek range operacja, którą wykonujesz. Kod źródłowy zawiera nawet TODO:

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

To wyjaśnia, dlaczego funkcja delete(k,v) nie zwalnia pamięci, po prostu usuwa ją z listy wiader, do których masz dostęp. Jeśli chcesz zwolnić rzeczywistą pamięć, musisz sprawić, że cała mapa będzie niedostępna, aby wkraczał w nią Śmieciarz. Możesz to zrobić używając linii

map = nil
 116
Author: Verran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-18 10:05:29

Zastanawiałem się, czy może dojść do wycieku pamięci. Więc napisałem program testowy:

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)
}
Wygląda na to, że GC do uwalnia pamięć. Więc w porządku.
 3
Author: vitvlkv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-14 07:22:58

Krótko mówiąc, tak. Zobacz poprzednie odpowiedzi.

I jeszcze to, z tutaj :

Ianlancetaylor skomentował 18 lutego 2015
Myślę, że kluczem do zrozumienia tego jest uświadomienie sobie, że podczas wykonywania instrukcji for/range, nie ma bieżącej iteracji. Istnieje zbiór wartości, które zostały zaobserwowane, oraz zbiór wartości, które nie zostały zaobserwowane. Podczas wykonywania ciała przypisano jedną z widocznych par klucz / wartość-ostatnią parę- do zmiennej(s) instrukcji range. Nie ma nic specjalnego w tej parze klucz/wartość, to tylko jedna z tych, które były już widoczne podczas iteracji.

Pytanie, na które odpowiada, dotyczy modyfikacji elementów mapy w czasie operacji range, dlatego wspomina o "bieżącej iteracji". Ale jest to również istotne tutaj: możesz usunąć klucze w zakresie, a to oznacza, że nie zobaczysz ich później w zakresie (a jeśli już je widziałeś, w porządku).

 0
Author: Larry Clapp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-08 21:57:46