Golang slice append and reallocation

Issue

I’ve been learning go recently and had a question about the behavior of slices when reallocation occurs. Assume I have a slice of pointers to a struct, such as:

var a []*A

If I were to pass this slice to another function, and my understanding is that internally this passes a slice header by value, that runs on a separate goroutine and just reads from the slice, while the function that launched the goroutine continues to append to the slice, is that a problem? For example:

package main

type A struct {
    foo int
}

func main() {
  a := make([]*A, 0, 100)
  ch := make(chan int)
  for i := 0; i < 100; i++ {
    a = append(a, &A{i})
  }
  go read_slice(a, ch)
  for i := 0; i < 100; i++ {
    a = append(a, &A{i+100})
  }
  <-ch
}

func read_slice(a []*A, ch chan int) {
   for i := range a {
     fmt.Printf("%d   ", a[i].foo)
   }
   ch <- 1
}

So from my understanding, as the read_slice() function is running on its own goroutine with a copy of the slice header, it has an underlying pointer to the current backing array and the size at the time it was called through which I can access the foo’s.

However, when the other goroutine is appending to the slice it will trigger a reallocation when the capacity is exceeded. Does the go runtime not deallocate the memory to the old backing array being used in read_slice() since there is a reference to it in that function?

I tried running this with “go run -race slice.go” but that didn’t report anything, but I feel like I might be doing something wrong here? Any pointers would be appreciated.

Thanks!

Solution

The GC does not collect the backing array until there are no references to the backing array. There are no races in the program.

Consider the scenario with no goroutines:

  a := make([]*A, 0, 100)
  for i := 0; i < 100; i++ {
    a = append(a, &A{i})
  }
  b := a
  for i := 0; i < 100; i++ {
    b = append(b, &A{i+100})
  }

The slice a will continue to reference the backing array with the first 100 pointers when append to b allocates a new backing array. The slice a is not left with a dangling reference to a backing array.

Now add the goroutine to the scenario:

  a := make([]*A, 0, 100)
  for i := 0; i < 100; i++ {
    a = append(a, &A{i})
  }
  b := a
  go read_slice(a, ch)
  for i := 0; i < 100; i++ {
    b = append(b, &A{i+100})
  }

The goroutine can happily use slice a. There’s no dangling reference.

Now consider the program in the question. It’s functionaly identical to the last snippet here.

Answered By – Bayta Darell

Answer Checked By – Gilberto Lyons (GoLangFix Admin)

Leave a Reply

Your email address will not be published.