10

Tôi đang học Go, và như một bài tập tôi muốn thực hiện một danh sách liên kết. Để tham khảo, tôi đã xem mã Go chính thức (https://golang.org/src/container/list/list.go). Một điều khiến tôi mắc kẹt là những dòng này:Thiết lập con trỏ tới nil để tránh rò rỉ bộ nhớ trong 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 } 

Tôi tò mò về cách thiết lập con trỏ thành 0 trong trường hợp này ngăn ngừa rò rỉ bộ nhớ? Nếu có thể tôi muốn xây dựng một chương trình có lỗ hổng này và nhìn thấy nó trong khi profiling với pprof (tôi sẽ sử dụng một verion sửa đổi của list.go mà không có thiết lập con trỏ nil này).


Để làm rõ câu trả lời: Nếu một trong các nút có một con trỏ bên ngoài để nó, sau đó tất cả các nút lấy liền kề sẽ có một tham chiếu hoạt động thông qua con trỏ đó và sẽ không được gỡ bỏ. enter image description here

  1. Chúng tôi tạo ra một con trỏ trỏ bên ngoài để node2
  2. Chúng tôi loại bỏ các nút 2-4 từ danh sách
  3. Bạn mong chờ vào thời điểm này chỉ dành cho các Node 1,2 & 5 được sống và phần còn lại là GC-ed. Tuy nhiên, do Node2 vẫn còn trỏ đến Node3 & vv, toàn bộ chuỗi vẫn không bị thu thập.

Trả lời

5

Giả định của bạn là chính xác. Nếu có một nhóm con trỏ trỏ đến nhau, nhưng không có tham chiếu/con trỏ đến bất kỳ thành viên nào của nhóm này, nhóm sẽ được phát hiện là không thể truy cập được bởi bộ thu gom rác và sẽ được giải phóng đúng cách.

Nhưng giải thích cho rò rỉ bộ nhớ rất đơn giản. Chúng ta có thể lấy các trình bao bọc list.Element từ danh sách chứa các con trỏ chưa được xuất hiện Element.nextElement.prev đến các phần tử tiếp theo và trước đó trong danh sách.

Khi xóa phần tử khỏi danh sách nếu các con trỏ này không được đặt thành nil, chúng sẽ giữ tham chiếu đến trình bao bọc phần tử tiếp theo và trước đó, bao gồm các giá trị được liên kết với các yếu tố đó.

Xem ví dụ này:

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 
} 

Trong listTest() chúng ta xây dựng một danh sách với 3 yếu tố, và chúng tôi lưu trữ các yếu tố thứ 2 trong một biến toàn cầu e2. Sau đó, chúng tôi loại bỏ yếu tố này. Bây giờ chúng tôi mong đợi rằng ngoại trừ e2 (và giá trị được bao bọc trong nó) mọi thứ khác sẽ được thu thập rác khi số tiền trả về listTest(), bởi vì danh sách không thể truy cập được bên ngoài chức năng listTest(). Có, chúng tôi có con trỏ ở e2 cho một phần tử, nhưng e2 có (phải có) không có gì để làm với danh sách nữa khi chúng tôi xóa nó.

Nếu prevnext con trỏ trong e2 sẽ không được thiết lập để nil, các giá trị được bọc trong các yếu tố chỉ bởi họ có thể không bao giờ được giải phóng, đệ quy. Nhưng kể từ khi thiết lập List.Remove() chính xác đến nil, trong ví dụ trên e1e3 - cùng với các giá trị được bao bọc trong chúng– sẽ được giải phóng (trên lần thu gom rác tiếp theo).

+0

Tôi đã bỏ qua câu hỏi với hình ảnh về những gì bạn mô tả, hãy sửa tôi nếu tôi sai. Tôi thực sự đã thử điều này với một phiên bản sửa đổi (với lỗi rò rỉ bộ nhớ) của danh sách và tôi có thể thấy nó không giải phóng bộ nhớ. – synepis

0

Bộ thu gom rác Golang dựa trên thuật toán đánh dấu và quét ba màu. Tóm lại, mọi bộ nhớ mà bạn sử dụng chương trình được liên kết với một màu. Màu sắc xác định xem bộ nhớ có bị cắt xén hay không.

Thuật toán này sẽ gắn cờ bộ nhớ được giải phóng nếu bộ nhớ này không được tham chiếu ở đâu đó (trực tiếp và gián tiếp). Nhưng nếu chúng ta xem mã:

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

Bản sao con trỏ này trong e.next into e.prev.next. Bây giờ, giả sử bạn muốn cập nhật e.prev.next bởi một phần tử được tạo hoàn toàn mới.

Phần tử đã xóa trước đó sẽ không bị thu gọn vì nó vẫn được tham chiếu bởi e.next.

Đây là lý do tại sao những dòng tồn tại:

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

này ngăn chặn rời khỏi tài liệu tham khảo cũ, và do đó ngăn chặn rò rỉ bộ nhớ.

+0

Tôi đã thêm nhận xét ở trên giải thích những gì tôi tin là sẽ xảy ra. Tôi không chắc chắn những gì bạn có ý nghĩa bởi "phần tử bị loại bỏ vẫn còn được tham chiếu bởi e.next", không phải là phần tử bị xóa? – synepis

Các vấn đề liên quan