10

Uczę się Go, a jako ćwiczenie chciałem zaimplementować połączoną listę. Dla porównania przyjrzałem się oficjalnemu kodowi Go (https://golang.org/src/container/list/list.go). Jedną rzeczą, która tkwi u mnie są te linie:Ustawianie wskaźników na zero, aby zapobiec wyciekom pamięci w Golang

108 // remove removes e from its list, decrements l.len, and returns e. 
    109 func (l *List) remove(e *Element) *Element { 
    110  e.prev.next = e.next 
    111  e.next.prev = e.prev 
    112  e.next = nil // avoid memory leaks 
    113  e.prev = nil // avoid memory leaks 
    114  e.list = nil 
    115  l.len-- 
    116  return e 
    117 } 

Jestem ciekaw, w jaki sposób ustalania wskaźników do zera w tym przypadku zapobiec wyciekom pamięci? Jeśli to możliwe, chciałbym skonstruować program, który ma tę wadę i zobaczyć go podczas profilowania z pprof (użyłbym zmodyfikowanej wersji list.go bez tego ustawienia wskaźnika zerowego).


W celu zwiększenia odpowiedzi: Jeśli jeden z węzłów ma wskaźnik poza nim, a następnie wszystkie sąsiednie usunięto węzły mają aktywną odniesienia w ten wskaźnik i nie zostaną usunięte. enter image description here

  1. Tworzymy zewnętrzny wskaźnik wskazujący Node2
  2. Usuwamy węzły 2-4 z listy
  3. można się spodziewać w tym momencie tylko dla węzła 1,2 & 5 być żywy a reszta do GC-ed. Jednak ze względu na Node2 nadal wskazując na Node3 & itp., Cały łańcuch pozostaje nieodebrany.

Odpowiedz

5

Twoje założenia są prawidłowe. Jeśli istnieje grupa wskaźników skierowanych do siebie nawzajem, ale nie ma odniesienia/wskaźnika do żadnego elementu tej grupy, grupa zostanie wykryta jako niedostępna przez moduł odśmiecania i zostanie uwolniona poprawnie.

Ale wyjaśnienie wycieku pamięci jest proste. Możemy pobrać opakowania z list.Element z listy zawierającej nieodportowane wskaźniki Element.next i Element.prev do następnych i poprzednich elementów na liście.

Podczas usuwania elementu z listy, jeśli te wskaźniki nie zostałyby ustawione na nil, zawierałyby odniesienia do następnych i poprzednich owijek elementów, w tym wartości skojarzone z tymi elementami.

Zobacz ten przykład:

var e2 *list.Element 

func main() { 
    listTest() 
    fmt.Println(e2.Value) 
    // At this point we expect everything from the list to be 
    // garbage collected at any time, we only have reference to e2. 
    // If e2.prev and e2.next would not be set to nil, 
    // e1 and e3 could not be freed! 
} 

func listTest() { 
    l := list.New() 
    e1 := l.PushBack(1) 
    e2 = l.PushBack(2) 
    e3 := l.PushBack(3) 
    // List is now [1, 2, 3] 
    fmt.Println(e1.Value, e2.Value, e3.Value) 
    l.Remove(e2) 
    // Now list is [1, 3], it does not contain e2 
} 

W listTest() budujemy listę z 3 elementów, a my przechowywać 2nd elementu w zmiennej globalnej e2. Następnie usuwamy ten element. Teraz możemy oczekiwać, że z wyjątkiem e2 (i wartości w nim zawinięte) wszystko inne zbiera śmieci, gdy zwraca się listTest(), ponieważ lista nie jest dostępna poza funkcją listTest(). Tak, mamy wskaźnik w e2 do elementu, ale e2 ma (powinien mieć) nic wspólnego z listą, ponieważ ją usunęliśmy.

Jeśli prev i next wskaźniki w e2 nie będzie ustawiony na nil wartości owinięte elementów wskazanych przez nich może być nigdy uwolniony, rekurencyjnie. Ale od List.Remove() poprawnie ustawia je na nil, w powyższym przykładzie e1 i e3 - wraz z wartościami w nich zapakowanymi - zostaną zwolnione (w następnym uruchomieniu zbierania śmieci).

+0

Jestem zadowolony z tego, co opisujesz, popraw mnie, jeśli się mylę. Tak naprawdę wypróbowałem to z wersją zmodyfikowanej (z błędem pamięci błędów) i widzę, że nie zwalnia ona pamięci. – synepis

0

Śmieciarka Golang bazuje na trójkolorowym algorytmie oznaczania i zamiatania. Krótko mówiąc, każda pamięć używana w programie jest powiązana z kolorem. Kolor określa, czy pamięć ma być zapisana w pamięci, czy nie.

Algorytm będzie oznaczał pamięć, która ma zostać zwolniona, jeśli ta pamięć nie ma gdzieś odniesienia (bezpośrednio i pośrednio). Ale jeśli spojrzymy na kod:

e.prev.next = e.next 
e.next.prev = e.prev 

Kopiujemy wskaźnik w e.next do e.prev.next. Teraz załóżmy, że chcesz zaktualizować e.prev.next o nowy, w pełni stworzony element.

Poprzednio usunięty element nie będzie wachlowany, ponieważ nadal jest przywoływany przez e.next.

Dlatego istnieją te linie:

e.next = nil // avoid memory leaks 
e.prev = nil // avoid memory leaks 

to uniemożliwić opuszczenie starych odniesień, a tym samym zapobiega przed wyciekiem pamięci.

+0

Dodałem komentarz powyżej wyjaśniający, co moim zdaniem się dzieje. Nie jestem pewien, co masz na myśli przez "usunięty element jest nadal przywoływany przez e.next", czy nie jest to usunięty element? – synepis