2015-02-10 13 views
11

Nếu tôi thực hiện một hàng đợi như thế này ...Không đi thu gom rác các phần của lát?

package main 

import(
    "fmt" 
) 

func PopFront(q *[]string) string { 
    r := (*q)[0] 
    *q = (*q)[1:len(*q)] 
    return r 
} 

func PushBack(q *[]string, a string) { 
    *q = append(*q, a) 
} 

func main() { 
    q := make([]string, 0) 

    PushBack(&q, "A") 
    fmt.Println(q) 
    PushBack(&q, "B") 
    fmt.Println(q) 
    PushBack(&q, "C") 
    fmt.Println(q) 

    PopFront(&q) 
    fmt.Println(q) 
    PopFront(&q) 
    fmt.Println(q)  
} 

... Tôi kết thúc với một mảng ["A", "B", "C"] mà không có lát trỏ đến hai yếu tố đầu tiên. Vì con trỏ "bắt đầu" của một slice không bao giờ có thể bị giảm đi (AFAIK), những phần tử này không bao giờ có thể được truy cập.

Bộ thu gom rác của Go có đủ thông minh để giải phóng chúng không?

Trả lời

17

Lát chỉ là mô tả (cấu trúc dữ liệu giống cấu trúc nhỏ) mà nếu không được tham chiếu sẽ được thu gom rác đúng cách.

Mảng cơ bản cho một lát (mà điểm mô tả để) mặt khác là chia sẻ giữa tất cả các lát mà được tạo ra bởi reslicing nó: trích dẫn từ Go Language Specification: Slice Types:

Một lát, khi được khởi tạo, luôn được liên kết với một mảng nằm bên dưới chứa các phần tử của nó. Do đó một slice chia sẻ lưu trữ với mảng của nó và với các lát khác của cùng một mảng; ngược lại, các mảng riêng biệt luôn đại diện cho bộ nhớ riêng biệt.

Do đó nếu ít nhất một lát hoặc một biến cho mảng nếu lát được tạo bằng cách cắt mảng, nó sẽ không bị thu gom rác.

Tuyên bố chính thức về vấn đề này:

Các bài viết trên blog Go Slices: usage and internals By Andrew Gerrand nêu rõ hành vi này:

Như đã đề cập trước đó, lại cắt một lát không làm cho một bản sao của mảng cơ bản. Mảng đầy đủ sẽ được lưu trong bộ nhớ cho đến khi nó không được tham chiếu nữa. Thỉnh thoảng điều này có thể khiến chương trình giữ tất cả dữ liệu trong bộ nhớ khi chỉ cần một phần nhỏ của dữ liệu đó.

...

Kể từ khi lát tham chiếu mảng ban đầu, chừng lát được giữ xung quanh các bộ thu rác không thể giải phóng các mảng.

Trở lại với ví dụ của bạn

Trong khi các mảng cơ bản sẽ không được trả tự do, lưu ý rằng nếu bạn thêm các yếu tố mới vào hàng đợi, được xây dựng trong append chức năng đôi khi có thể phân bổ một mảng mới và sao chép các phần tử hiện tại cho mới - nhưng sao chép sẽ chỉ sao chép các phần tử của slice chứ không phải toàn bộ mảng cơ bản! Khi sự tái phân bổ và sao chép như vậy xảy ra, mảng "cũ" có thể là rác được thu thập nếu không có tham chiếu nào khác tồn tại. Một điều rất quan trọng nữa là nếu một phần tử xuất hiện từ phía trước, slice sẽ được tạo lại và không chứa tham chiếu đến phần tử xuất hiện, nhưng vì mảng nằm bên dưới vẫn chứa giá trị đó, giá trị cũng sẽ vẫn giữ nguyên. trong bộ nhớ (không chỉ mảng).Đó là khuyến cáo rằng bất cứ khi nào một phần tử được popped hoặc loại bỏ khỏi hàng đợi của bạn (slice/array), luôn luôn bằng không nó (phần tử tương ứng của nó trong slice) để giá trị sẽ không còn trong bộ nhớ không cần thiết. Điều này trở nên quan trọng hơn nếu slice của bạn chứa các con trỏ tới các cấu trúc dữ liệu lớn.

func PopFront(q *[]string) string { 
    r := (*q)[0] 
    (*q)[0] = "" // Always zero the removed element! 
    *q = (*q)[1:len(*q)] 
    return r 
} 
+7

Để hoàn icza câu trả lời, và đối với trường hợp xếp hàng cụ thể của bạn: phần không thể truy cập của slice sẽ không bị thu gom rác, tuy nhiên, khi bạn 'PushBack' một mục mới và' q' đã đầy, bản sao mới của 'q' sẽ được cấp phát, và trong trường hợp này, ** chỉ các phần tử có thể truy cập ** sẽ được sao chép, vì vậy A, B và C sẽ không được sao chép. 'q' sẽ không phát triển mãi mãi với các yếu tố không thể truy cập được. – siritinga

+0

@siritinga Đó là sự thật, gợi ý tốt. Cảm ơn. Kết hợp nó vào câu trả lời với các thông tin quan trọng bổ sung (như zeroing "slot" nếu một phần tử bị loại bỏ). – icza

0

câu hỏi đơn giản, câu trả lời đơn giản: Số (. Nhưng nếu bạn tiếp tục đẩy slice sẽ tại một số điểm tràn mảng cơ bản của nó sau đó các yếu tố không sử dụng trở nên có sẵn để được giải phóng)