In Go, when will a variable become unavailable?

Go 1.7 beta 1 was released this morning, here is the version 1.7 release project. A new KeepAlive function has been added to the runtime package. The runtime.KeepAlive document provides an example:

 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(pd) }) var buf [10]byte n, err := syscall.Read(pd, buf[:]) // Ensure p is not finalized until Read returns. runtime.KeepAlive(p) // No more uses of p after this point. 

The runtime.SetFinalizer document also gave an explanation about runtime.KeepAlive :

For example, if p points to a structure containing a file descriptor d and p has a finalizer that closes this file descriptor, and if the last use of p in a function is a call to syscall.Write (pd, buf, size), then p may not be available, as only program will enter syscall.Write. The finalizer can work at this moment, closing pd, causing syscall.Write to crash, because it writes a descriptor to a closed file (or, even worse, to a completely different file descriptor opened by a different goroutine). To avoid this problem, call runtime.KeepAlive (p) after calling syscall.Write.

What confused me was that the variable p has not yet left its life scale, why is it inaccessible? Does this mean that the variable will be unavailable, unless it is used in the following code, regardless of whether it is in its living space?

+5
source share
1 answer

A variable becomes unavailable when the runtime detects that Go code cannot reach the point at which this link refers again.

In the above example, syscall.Open() is used to open the file. The returned file descriptor (which is only an int value) is wrapped in a struct . Then, a finalizer is attached to this struct value, which closes the file descriptor. Now that this structure value becomes unavailable, its finalizer can be started at any time, and closing / invalidating / reusing the file descriptor may lead to unexpected behavior or errors when executing syscall Read() .

The last use of this struct p value in Go code when syscall.Read() called (and the file descriptor pd is passed to it). The syscall implementation will use this file descriptor after initializing syscall.Read() , it can do this until syscall.Read() returns. But this use of the file descriptor is "independent" of the Go code.

Thus, the value of struct p not used at run time syscall, and syscall blocks the Go code until it returns. This means that Go’s runtime allows p to be marked as unreachable at the time of Read() (until Read() returns) or even before it actually runs (since p used only to provide arguments for calling Read() .

Therefore, the call to runtime.KeepAlive() : since this call is after syscall.Read() and refers to the variable p , Go runtime is not allowed to mark p inaccessible until Read() returns, because this happens after the call to Read() .

Note that you can use other constructs to “keep p alive,” for example. _ = p or return it. runtime.KeepAlive() does nothing magical in the background, its implementation is as follows:

 func KeepAlive(interface{}) {} 

runtime.KeepAlive() provides a much better alternative, because:

  • The docs make it clear that we want to keep p alive (to prevent Finalizers from running).
  • Using other constructs, such as _ = p , may be "optimized" by future compilers, but does not call runtime.KeepAlive() .
+7
source

All Articles