[dictionary] 범위 루프 내의지도에서 선택한 키를 제거하는 것이 안전합니까?

지도에서 선택한 키를 어떻게 제거 할 수 있습니까? delete()아래 코드와 같이 범위와 결합 하는 것이 안전 합니까?

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



답변

이것은 안전합니다! Effective Go 에서도 비슷한 샘플을 찾을 수 있습니다 .

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

그리고 언어 사양 :

맵에 대한 반복 순서는 지정되지 않았으며 한 반복에서 다음 반복으로 동일하지는 않습니다. 반복 중에 아직 도달하지 않은 맵 항목이 제거 되면 해당 반복 값이 생성되지 않습니다. 반복 중에 맵 항목이 작성 되면 해당 항목이 반복 중에 생성되거나 건너 뛸 수 있습니다. 선택은 생성 된 각 항목과 반복마다 다를 수 있습니다. 맵이 nil 인 경우 반복 횟수는 0입니다.


답변

Sebastian의 답변은 정확하지만 안전한지 알고 싶었 으므로 Map 소스 코드를 파헤 쳤습니다 . 에 대한 호출 에서처럼 보이지만 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


답변

메모리 누수가 발생할 수 있는지 궁금합니다. 그래서 테스트 프로그램을 작성했습니다.

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가 메모리를 비우는 것처럼 보입니다. 괜찮습니다.


답변

한마디로 그렇습니다. 이전 답변을 참조하십시오.

또한 여기에서 :

ianlancetaylor는 2015 년 2 월 18 일에 댓글을 달았습니다.
이를 이해하는 열쇠는 for / range 문의 본문을 실행하는 동안 현재 반복이 없다는 것을 인식하는 것입니다. 보인 값 세트와 보지 않은 값 세트가 있습니다. 본문을 실행하는 동안 가장 최근에 본 키 / 값 쌍 중 하나가 range 문의 변수에 할당되었습니다. 해당 키 / 값 쌍에는 특별한 것이 없으며 반복 중에 이미 본 것 중 하나 일뿐입니다.

그가 대답하는 질문은 range작업 중에 적절한 맵 요소를 수정 하는 것입니다. 이것이 그가 “현재 반복”을 언급하는 이유입니다. 그러나 그것은 또한 관련이 있습니다 : 당신은 범위 동안 키를 삭제할 수 있으며, 그것은 당신이 그 범위에서 나중에 볼 수 없다는 것을 의미합니다 (그리고 이미 보았 으면 괜찮습니다).


답변