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


135

지도에서 선택한 키를 어떻게 제거 할 수 있습니까? 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

답변:


174

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

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

그리고 언어 사양 :

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


key.expired undefined (유형 문자열에 필드 또는 메소드가 만료되지 않음)

4
@kristen-위에서 설명한 예제에서 키는 문자열이 아니라 func (a T) expired() bool인터페이스 를 구현하는 일부 사용자 정의 유형이어야합니다 . 이 예의 목적으로 다음을 시도해 볼 수 있습니다. m := make(map[int]int) /* populate m here somehow */ for key := range (m) { if key % 2 == 0 { /* this is just some condition, such as calling expired */ delete(m, key); } }
abanana

매우 혼란 스럽습니다.
g10guang

150

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

2
따라서 현재의 값뿐만 아니라 임의의 값을 맵에서 삭제하는 것이 안전하다고 말하는 것처럼 들립니다. 그리고 이전에 임의로 삭제 한 해시를 평가할 때 안전하게 건너 뛸 것입니까?
Flimzy

@Flimzy이 놀이터 play.golang.org/p/FwbsghzrsO 에서 볼 수 있듯이 맞습니다 . 삭제 한 인덱스가 범위의 첫 번째 인덱스 인 경우 k, v에 이미 기록 된 이후에도 해당 인덱스가 계속 표시되지만 범위를 찾은 첫 번째 인덱스 이외의 인덱스로 설정하면 두 개의 키만 표시됩니다. 공황이 아닌 3 대신 / value 쌍.
Verran

1
"실제로 메모리를 확보하지 않습니까?"는 여전히 관련이 있습니까? 주석이있는 소스를 찾으려고했지만 찾을 수 없습니다.
Tony

11
중요 사항 : 이것은 현재 구현 일 뿐이며 향후 변경 될 수 있으므로 "지원"하는 것처럼 보일 수있는 추가 속성에 의존해서는 안됩니다. 당신이 가진 유일한 보증은 사양에 의해 제공되는입니다 세바스찬의 대답에 설명 된대로 . (이것은 바둑 내부를 탐구하고 설명하는 것이 흥미롭고 교육적이며 일반적으로 훌륭
하다는 것입니다

4

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

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


0

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

또한 여기에서 :

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.