【问题标题】:Golang slice append and reallocationGolang 切片追加和重新分配
【发布时间】:2024-01-23 08:55:01
【问题描述】:

我最近一直在学习 go,并且对重新分配发生时切片的行为有疑问。假设我有一片指向结构的指针,例如:

var a []*A

如果我要将这个切片传递给另一个函数,我的理解是在内部这会按值传递切片头,它在单独的 goroutine 上运行并且只是从切片中读取,而启动 goroutine 的函数继续附加到切片,这是一个问题吗?例如:

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
}

因此,据我了解,由于 read_slice() 函数在其自己的 goroutine 上运行并带有切片标头的副本,因此它具有指向当前支持数组的底层指针以及调用它时的大小,我通过它可以访问 foo 的。

但是,当另一个 goroutine 附加到 slice 时,它​​会在超出容量时触发重新分配。 go 运行时是否不会将内存释放给 read_slice() 中使用的旧后备数组,因为该函数中有对它的引用?

我尝试使用“go run -race slice.go”运行它,但没有报告任何内容,但我觉得我可能在这里做错了什么?任何指针将不胜感激。

谢谢!

【问题讨论】:

    标签: go slice goroutine


    【解决方案1】:

    在没有对后备数组的引用之前,GC 不会收集后备数组。该计划中没有比赛。

    考虑没有 goroutine 的场景:

      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})
      }
    

    当追加到b 分配新的后备数组时,切片a 将继续使用前100 个指针引用后备数组。切片 a 没有留下对后备数组的悬空引用。

    现在将 goroutine 添加到场景中:

      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})
      }
    

    goroutine 可以愉快地使用 slice a。没有悬而未决的参考。

    现在考虑问题中的程序。它的功能与这里的最后一个 sn-p 相同。

    【讨论】:

    • 感谢您的快速回复!