一尘不染

在Go中,变量何时将变得不可访问?

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.

的文档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还没有离开它的生存范围,为什么它不可达?这是否意味着如果在以下代码中仅使用变量,则无论变量是否在其生存范围内,都将无法访问该变量?


阅读 319

收藏
2020-07-02

共1个答案

一尘不染

当运行时检测到 Go 代码无法到达再次引用该变量的位置时,变量将变得不可访问。

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

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

因此,p在执行系统调用期间不会使用struct值,并且系统调用会阻塞Go代码,直到返回为止。这意味着Go运行时p在执行过程中Read()Read()返回之前)或
什至在其实际执行开始之前 被标记为不可访问(因为p仅用于提供call的参数)Read()

因此,对runtime.KeepAlive():的调用由于在该调用
之后syscall.Read()并且它 引用
了变量p,因此不允许Go运行时pRead()返回之前将其标记为不可访问,因为在Read()调用之后。

请注意,您可以使用其他构造来“保持p活动”,例如_ = p返回它。runtime.KeepAlive()在后台没有任何神奇的事情,其实现是:

func KeepAlive(interface{}) {}

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

  • 它清楚地记录了我们要保持p生存的状态(以防止Finalizer运行)。
  • 使用其他结构(例如)_ = p可能会被将来的编译器“优化”,但不会被runtime.KeepAlive()调用。
2020-07-02