在Go中,变量何时无法访问?

时间:2016-06-02 10:01:13

标签: go garbage-collection

Go 1.7 beta 1今天早上发布,现在是the release notes draft of Go 1.7。新功能KeepAlive已添加到包runtimeThe doc of runtime.KeepAlive举了一个例子:

type File struct { d int }
d, err := syscall.Open("/file/path", syscall.O_RDONLY, 0)
// ... do something if err != nil ...
p := &FILE{d}
runtime.SetFinalizer(p, func(p *File) { syscall.Close(p.d) })
var buf [10]byte
n, err := syscall.Read(p.d, buf[:])
// Ensure p is not finalized until Read returns.
runtime.KeepAlive(p)
// No more uses of p after this point.

The doc of runtime.SetFinalizer也对runtime.KeepAlive

作了解释
  

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

令我感到困惑的是,变量p尚未离开其生命范围,为什么它无法到达?这是否意味着如果变量在以下代码中没有使用它将无法访问,无论它是否在其生命范围内?

1 个答案:

答案 0 :(得分:9)

当运行时检测到 Go 代码无法到达再次引用该变量的点时,变量无法访问。

在您发布的示例中,syscall.Open()用于打开文件。返回的文件描述符(只是int值)是"包装"在struct中。然后将一个终结器附加到此结构值,该结构值将关闭文件描述符。现在,当此结构值变得无法访问时,其终结器可能随时运行,并且文件描述符的关闭/无效/重新使用可能会导致Read()系统调用执行中的意外行为或错误。

Go 代码中最后一次使用此结构值p是在调用syscall.Read()时(文件描述符p.d被传递给它)。系统调用的实现将在syscall.Read()启动之后使用该文件描述符,它可以在syscall.Read()返回之前执行。但是这种文件描述符的使用是"独立的" Go代码。

因此在执行syscall期间不使用结构值p,并且syscall会阻止Go代码,直到它返回为止。这意味着允许Go运行时在执行p期间(Read()返回之前)将Read()标记为无法访问,或者甚至在其实际执行开始之前(因为p仅用于提供调用Read()的参数。

因此调用runtime.KeepAlive():因为此调用是之后 syscall.Read()引用变量p,在p返回之前,不允许Go运行时标记Read()无法访问,因为这是在Read()调用之后。

请注意,您可以使用其他构造来保持p活着",例如_ = p或将其归还。 runtime.KeepAlive()在背景中没有任何神奇之处,它的实现是:

func KeepAlive(interface{}) {}

runtime.KeepAlive()提供了更好的选择,因为:

  • 这显然是我们希望p保持活跃的文档(以防止Finalizers的运行。)
  • 使用_ = p之类的其他结构可能会得到优化"未来的编译器,但不是runtime.KeepAlive()来电。