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

hur*_*ron 7 garbage-collection go

Go 1.7 beta 1今天早上发布,这是Go 1.7的发行说明草案.KeepAlive包中添加了一个新功能runtime.该文件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.
Run Code Online (Sandbox Code Playgroud)

该文件runtime.SetFinalizer还给出了一个解释runtime.KeepAlive:

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

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

icz*_*cza 9

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

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

在一次使用这种结构值的p围棋代码时syscall.Read()被调用(和文件描述符p.d传递给它).系统调用的实现将使用文件描述符后开始syscall.Read(),它可以这样做,直到syscall.Read()回报.但是这种文件描述符的使用是与Go代码"独立"的.

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

因此,该呼叫runtime.KeepAlive():因为这个调用之后syscall.Read(),它引用变量p,Go运行时是不允许的,以纪念p前无法到达Read()的回报,因为这是后Read()调用.

请注意,您可以使用其他构造来"保持p活力",例如_ = p或返回它.runtime.KeepAlive()在后台没有什么神奇之处,它的实现是:

func KeepAlive(interface{}) {}
Run Code Online (Sandbox Code Playgroud)

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

  • 它显然是我们希望保持p活力的文件(以防止终结者的运行).
  • 使用其他结构_ = p可能会被未来的编译器"优化"出来,而不是runtime.KeepAlive()调用.