错误还是功能?与Golang中的'range'和'channel'相关的垃圾收集

时间:2017-09-07 16:00:19

标签: go garbage-collection

package main

import (
   "sync"
   "runtime"
)

type S struct {
   chs chan int
}

var wg sync.WaitGroup

func worker(s *S) {
   for i := range s.chs {
      println("In worker, ch = ", i)
   }

   wg.Done()
}

func main() {
   s := S{make(chan int)}

   runtime.SetFinalizer(&s, func(ss *S) {
      println("Finalizer")
      close(ss.chs)
   })


   wg.Add(1)

   go worker(&s)
   for i := 0; i < 1; i++ {
      s.chs <- 1
   }

   runtime.GC()

   wg.Wait()
}

输出(转1.8.3):

  

在工人中,ch = 1

     

终结

我希望这个程序陷入僵局。 runtime.GC()不会收集s,因为worker()包含对s.chs的引用。

然而,它以第1.8.3节结束。在s的终结器中,甚至close(s.chs)成功调用。

我想知道它是否与range和GC有关。

非常感谢。

1 个答案:

答案 0 :(得分:1)

我不是100%确定这是发生了什么,而是来自runtime godoc,SetFinalizer的第二段到最后一段:

  

例如,如果p指向包含文件描述符的结构   d,并且p有一个关闭该文件描述符的终结器,如果是   最后在函数中使用p是对syscall.Write的调用(p.d,buf,   大小),一旦程序进入,p可能无法访问   syscall.Write。终结者可以在那一刻运行,关闭p.d,   导致syscall.Write失败,因为它正在写入已关闭的文件   描述符(或者更糟糕的是,打开了一个完全不同的文件描述符   由不同的goroutine)。要避免此问题,请致电   调用syscall.Write之后运行时.KeepAlive(p)。

对我来说,Finalizer可能会在最后一次使用var之后立即运行。

我自己从来没有想过这种可能性,但从技术上讲,GC可以知道它是否可以在变量范围消失之前收集变量。考虑这个简单的例子。

func main() {
    str := "Hello World"
    fmt.Println(str)
    someMainLoop()
    // nothing after this, but someMainLoop() continues until stopped manually
}

GC无法收集str没有理由。它永远不会再次使用,它很有可能知道这一点。