Is it safe to delete selected keys from a Golang card in a range loop?

How to remove selected keys from the Golang card? Is it safe to combine delete () with a range, as in the code below?

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) } } 
+72
for-loop go map
Apr 22 '14 at 20:52
source share
3 answers

It's safe! You can also find a similar pattern in Effective Stroke :

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

And language specification :

The order of iterations on the cards is not specified and cannot be the same from one iteration to the next. If map entries that have not yet been reached are deleted during the iteration , the corresponding iteration values ​​will not be created. If map entries were created during the iteration , this entry may be obtained during the iteration or may be skipped. The selection may differ for each record created and from one iteration to the next. If the mapping is zero, the number of iterations is 0.

+94
Apr 22 '14 at 21:19
source share

Sebastian's answer is accurate, but I wanted to know why it was safe, so I dug a little into the map source code . It seems that when calling delete(k, v) it just sets a flag (and also changes the counter value) instead of actually deleting the value:

 b->tophash[i] = Empty; 

(Empty is constant for value 0 )

What the map apparently does is allocating a certain number of buckets depending on the size of the map, which grows when inserts are executed at a speed of 2^B (from this source code ):

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

This way, almost always more buckets are allocated than you use, and when you do range on the map, it checks the tophash value of each bucket in that 2^B to see if it can skip over it.

To summarize, delete within range is safe because the data is technically still there, but when he checks tophash , he sees that he can just skip it and not include the range that you are executing in it. The source code even includes TODO :

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

This explains why using the delete(k,v) function does not actually free up memory, but simply removes it from the list of buckets you have access to. If you want to free the actual memory, you will need to make the entire card inaccessible so that it includes garbage collection. You can do this using a string like

 map = nil 
+108
Apr 22 '14 at 22:37
source share

I was wondering if a memory leak could occur. So I wrote a test program:

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

Looks like GC does free memory. So everything is all right.

+2
Sep 14 '16 at 7:22
source share



All Articles