Установка указателей на ноль, чтобы предотвратить утечку памяти в Голанге

memory-management go memory-leaks garbage-collection memory-profiling

2092 просмотра

2 ответа

Я изучаю Go, и в качестве упражнения я хотел реализовать связанный список. Для справки я посмотрел официальный код Go ( https://golang.org/src/container/list/list.go ). Одна вещь, которая застряла со мной, это эти строки:

   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  } 

Мне любопытно, как установка указателей на ноль в этом случае предотвращает утечки памяти? Если возможно, я хотел бы создать программу, которая имеет этот недостаток, и увидеть ее во время профилирования с помощью pprof (я бы использовал модифицированную версию list.go без этой установки указателя nil).


Для ясности ответа : если один из узлов имеет внешний указатель на него, то все смежные удаленные узлы будут иметь активную ссылку через этот указатель и не будут удалены. введите описание изображения здесь

  1. Мы создаем внешний указатель, указывающий на Node2
  2. Удаляем узлы 2-4 из списка
  3. В этот момент вы ожидаете, что только Node 1,2 и 5 будут живы, а остальные будут GC-ed. Однако из-за того, что Node2 все еще указывает на Node3 и т. Д., Вся цепочка остается невыбранной.
Автор: synepis Источник Размещён: 08.11.2019 11:10

Ответы (2)


10 плюса

Решение

Ваши предположения верны. Если существует группа указателей, указывающих друг на друга, но нет ссылки / указателя на какого-либо члена этой группы, эта группа будет обнаружена сборщиком мусора как недоступная и будет освобождена должным образом.

Но объяснение утечки памяти просто. Мы можем получить list.Elementоболочки из списка, которые содержат неэкспортированные Element.nextи Element.prevуказатели на следующий и предыдущий элементы в списке.

При удалении элемента из списка, если эти указатели не будут установлены nil, они будут содержать ссылки на следующий и предыдущий упаковщики элементов, включая значения, связанные с этими элементами.

Смотрите этот пример:

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
}

В listTest()мы строим список с 3 -х элементов, и мы сохраняем 2 - го элемента в глобальной переменной e2. Затем мы удаляем этот элемент. Теперь мы можем ожидать, что кроме e2(и значения, заключенного в нем), все остальное будет собирать мусор при listTest()возврате, потому что список недоступен вне listTest()функции. Да, у нас есть указатель e2на элемент, но он больше e2не должен иметь отношения к списку, поскольку мы его удалили.

Если указатели prevи и не будут установлены в значения, значения, обернутые указанными ими элементами, никогда не смогут быть освобождены рекурсивно. Но поскольку они правильно установлены , в приведенном выше примере и - вместе со значениями, заключенными в них, - будут освобождены (при следующем запуске сборки мусора).nexte2nilList.Remove()nile1e3

Автор: icza Размещён: 20.08.2016 05:28

0 плюса

Сборщик мусора Golang основан на трехцветном алгоритме разметки и очистки. Короче говоря, каждая память, которую вы используете в программе, связана с цветом. Цвет определяет, будет ли память одета или нет.

Этот алгоритм помечает память, которая должна быть освобождена, если на эту память не ссылаются где-либо (прямо или косвенно). Но если мы посмотрим на код:

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

Это скопирует указатель в e.next в e.prev.next. Теперь предположим, что вы хотите обновить e.prev.next новым полностью созданным элементом.

Ранее удаленный элемент не будет обработан, потому что на него все еще ссылается e.next.

Вот почему эти строки существуют:

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

Это предотвращает оставление старых ссылок и, таким образом, предотвращает утечки памяти.

Автор: jnmoal Размещён: 20.08.2016 12:51
Вопросы из категории :
32x32