You need to know about the new features in Go 1.24: cleaning and weak indicators

in Last year’s blog post around unique
Package, we have mentioned some new features and then in reviewing the suggestion, and we are excited to share it as of Go 1.24, it is now available to all Goo developers. These new features are runtime.AddCleanup
The job, which you photograph a job to run when the object is not able to reach it, and weak.Pointer
The type, which safely indicates an object without preventing it from collecting garbage. Together, these two features are strong enough to build your own unique
eviction! Let’s dig what makes these features useful, and when we use them.
Note: These new features are advanced features of the garbage mosque. If you are not already accustomed to the concepts of basic garbage collection, we strongly recommend reading our introduction Garbage Mosque Directory.
Cleaning
If you have been used by Finalizer, the concept of cleaning will be familiar. The final farm is a function, linked to an intended object by calling runtime.SetFinalizer
And this is later called by the garbage mosque at some point after the object becomes unconnected. At a high level, cleaning works in the same way.
Let’s consider an application that uses a file set for memory, and see how cleaning can help.
//go:build unix
type MemoryMappedFile struct {
data []byte
}
func NewMemoryMappedFile(filename string) (*MemoryMappedFile, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
// Get the file's info; we need its size.
fi, err := f.Stat()
if err != nil {
return nil, err
}
// Extract the file descriptor.
conn, err := f.SyscallConn()
if err != nil {
return nil, err
}
var data []byte
connErr := conn.Control(func(fd uintptr) {
// Create a memory mapping backed by this file.
data, err = syscall.Mmap(int(fd), 0, int(fi.Size()), syscall.PROT_READ, syscall.MAP_SHARED)
})
if connErr != nil {
return nil, connErr
}
if err != nil {
return nil, err
}
mf := &MemoryMappedFile{data: data}
cleanup := func(data []byte) {
syscall.Munmap(data) // ignore error
}
runtime.AddCleanup(mf, cleanup, data)
return mf, nil
}
A file set to memory its contents is set on memory, in this case, the basic data of the byte chip. Thanks to the magic of the operating system, he reads and writes to the ball slice access directly to the file contents. With this symbol, we can pass around *MemoryMappedFile
And when it is no longer referred to, the memory maps that we created will be cleaned.
Note that runtime.AddCleanup
It takes three mediators: a variable address to attach cleaning to the cleaning function itself, and an argument for the cleaning function. Main difference between this job and runtime.SetFinalizer
It is that the cleaning function takes a different medium from the object that we attach to cleaning. This change fixes some problems with the finals.
It is not a secret It is difficult to use the final cultivation properly. For example, the organisms that are attached to any reference cycles should not participate (even the indicator for itself too much!), Otherwise, the object will never be recovered and the final farm will never be run, causing leakage. Final flowers also greatly delay memory reclamation. It takes two sessions to collect the full garbage to restore memory for a final object: one to determine that it cannot be accessed, and the other to determine that it is still not accessible after implementing the finals.
The problem is that retirees revive the organisms that are attached to it. Finalizer is not running so that the object cannot be reached, and then it is considered “dead”. But since the final implant is called with an indicator to the object, the garbage mosque should prevent the reclamation of the memory of that object, and instead it should generate a new reference to retreat, which makes it accessible, or “direct”, again. This reference may remain even after the return of Finalizer, for example if Finalizer wrote to a global variable or sends it via a channel. The resurrection of creatures is a problem because they mean the object, and everything that refers to it, and all these beings refer to, and so on, can be reached, even if it is collected as a garbage.
We solve both problems by not passing the original object to the cleaning function. First, it is not necessary to keep the values that you do not need to be reached in particular by the garbage mosque, so that it is still possible to recover the object even if it is involved in a cycle. Second, since the object is not necessary for cleaning, its memory can be recovered immediately.
Weak indicators
Returning to the example of our memory file, assume that we notice that our program frequently draws the same files over and over again, from different goroutines unlawful to each other. This is good from the memory perspective, because all of these appointments will share the actual memory, but they lead to a lot of unnecessary system calls to determine the file and cancel the file composition. This is particularly bad if only all Goorotin reads a small section of each file.
Therefore, let’s draw appointments via the file name. (Suppose our program only reads from appointments, and the files themselves are never modified or renamed as soon as they are created. These assumptions are reasonable for system lines files, for example.)
We can keep a map from the file name to the memory set, but then it becomes unclear when it is safe to remove entries from that map. We can barely Use a cleaning process, if this is not the case that the entry of the map itself will keep the file that was set to be alive.
Weak indicators solve this problem. The weak indicator is a special type of indicator that the garbage mosque ignores when determining whether an object can be reached. Go to 1.24 new, weak indicator type, weak.Pointer
He has Value
The way is either a real indicator if the object is still capable of reaching it, or nil
If not.
If we instead maintain a map only weak It indicates the file set for memory, we can clean the entry of the map when no one uses it anymore! Let’s see what this looks.
var cache sync.Map // map[string]weak.Pointer[MemoryMappedFile]
func NewCachedMemoryMappedFile(filename string) (*MemoryMappedFile, error) {
var newFile *MemoryMappedFile
for {
// Try to load an existing value out of the cache.
value, ok := cache.Load(filename)
if !ok {
// No value found. Create a new mapped file if needed.
if newFile == nil {
var err error
newFile, err = NewMemoryMappedFile(filename)
if err != nil {
return nil, err
}
}
// Try to install the new mapped file.
wp := weak.Make(newFile)
var loaded bool
value, loaded = cache.LoadOrStore(filename, wp)
if !loaded {
runtime.AddCleanup(newFile, func(filename string) {
// Only delete if the weak pointer is equal. If it's not, someone
// else already deleted the entry and installed a new mapped file.
cache.CompareAndDelete(filename, wp)
}, filename)
return newFile, nil
}
// Someone got to installing the file before us.
//
// If it's still there when we check in a moment, we'll discard newFile
// and it'll get cleaned up by garbage collector.
}
// See if our cache entry is valid.
if mf := value.(weak.Pointer[MemoryMappedFile]).Value(); mf != nil {
return mf, nil
}
// Discovered a nil entry awaiting cleanup. Eagerly delete it.
cache.CompareAndDelete(filename, value)
}
}
This example is somewhat complicated, but its essence is simple. We start with a simultaneous global map for all the designated files we have done. NewCachedMemoryMappedFile
This map consults a specific file, and if this fails, it creates and tries to include a new appointment file. This may also fail, of course, as we are racing with other inclusion, so we need to be careful as well, and to try. (This design has a defect in that we may waste the same file several times in a race, and we will have to remove it by cleaning that was added by NewMemoryMappedFile
. Perhaps this is not a big problem most of the time. It is left as an exercise for the reader.)
Let’s take a look at some of the beneficial features of the weak indicators and the cleaning of this symbol.
First, note that the weak indicators are comparable. Not only that, the weak indicators have a stable and independent identity, which remain even after the things they refer to have long been. That is why it is safe to contact the cleaning function sync.Map
‘s CompareAndDelete
Who compares weak.Pointer
The decisive reason for this symbol at all.
Second, note that we can add multiple independent cleaning operations to one MemoryMappedFile
goal. This allows us to use cleaning manner and use them to create general data structures. In this particular example, the plural may be more efficient NewCachedMemoryMappedFile
with NewMemoryMappedFile
And made them share cleaning. However, the feature of the code we wrote above is that it can be rewritten in general!
type Cache[K comparable, V any] struct {
create func(K) (*V, error)
m sync.Map
}
func NewCache[K comparable, V any](create func(K) (*V, error)) *Cache[K, V] {
return &Cache[K, V]{create: create}
}
func (c *Cache[K, V]) Get(key K) (*V, error) {
var newValue *V
for {
// Try to load an existing value out of the cache.
value, ok := cache.Load(key)
if !ok {
// No value found. Create a new mapped file if needed.
if newValue == nil {
var err error
newValue, err = c.create(key)
if err != nil {
return nil, err
}
}
// Try to install the new mapped file.
wp := weak.Make(newValue)
var loaded bool
value, loaded = cache.LoadOrStore(key, wp)
if !loaded {
runtime.AddCleanup(newValue, func(key K) {
// Only delete if the weak pointer is equal. If it's not, someone
// else already deleted the entry and installed a new mapped file.
cache.CompareAndDelete(key, wp)
}, key)
return newValue, nil
}
}
// See if our cache entry is valid.
if mf := value.(weak.Pointer[V]).Value(); mf != nil {
return mf, nil
}
// Discovered a nil entry awaiting cleanup. Eagerly delete it.
cache.CompareAndDelete(key, value)
}
}
Warnings and future work
Although our best, cleaning and weak indicators can still be at risk. To direct those who are considering using final scales, cleaning, and weak indicators, we recently updated Garbage Mosque Directory With some tips on using these features. Take a look the next time you reach them, but also think carefully if you need to use it at all. These are advanced tools with hidden connotations, and as the guide says, most Go code is indirectly benefiting from these features, not using them directly. Adhere to use situations where these features shine, and you will be fine.
Currently, we will contact some problems that are likely to encounter.
First, the object in which the cleaning is connected must be accessed from the cleaning function (as a variable captured) or the intermediate to the cleaning function. Each of these cases never leads to cleaning. (In the special case of the cleaning argument, being exactly that was transferred to the indicator runtime.AddCleanup
and runtime.AddCleanup
The panic will, as a sign of the caller that they should not use cleaning operations in the same way as the finals are used.)
Second, when using weak indicators as keys to the map map, the reference object should not be weakly reached from the value of the corresponding map, otherwise the object will remain live. This may seem clear when it is a depth inside a blog post about weak indicators, but it is easy to miss. This problem has inspired the complete concept of the astronomical opening to solve it, which is a possible future trend.
Third, a common pattern is needed with cleaning that a cover object is needed, as we see here with MemoryMappedFile
example. In this particular case, you can imagine that the garbage mosque directly tracks the specific memory area and passes around the interior []byte
. This function is a possible future work, and it has recently been done Proposal.
Finally, both weak indicators and cleaning are not defined by their nature, and their behavior depending on the design and dynamics of the garbage mosque. Cleaning documents to the garbage mosque have never allowed cleaning operations at all. The programming instructions test can be effective that it uses is difficult, however It is possible.
Why now?
Weak indicators have been put up as a feature of Go from the beginning, but for years their priorities have not been determined by the Go team. One of the reasons for this is that it is hidden, and the space of the weak indicators is a minefield of decisions that can make it more difficult to use. Another is that weak indicators are a specialized tool, while simultaneously adding the complexity to the language. We already had experience with the pain SetFinalizer
It can be for use. But there are some useful programs that cannot be expressed without, and unique
A package and the reasons for its existence truly confirmed this.
With limestone, it is too late from beginners, visions of all great works since they were carried out by teams in other languages such as C and Java, designs for weak forms and cleaning rapidly collected. The desire to use weak indicators with finals has sparked additional questions, so the design runtime.AddCleanup
Soon it came together.
Thanks and appreciation
I would like to thank everyone in society who contributed to the proposal notes and raising insects when the features are available. I would also like to thank David Chis for comprehensive thinking through the significant indications runtime.AddCleanup
. I would like to thank Carlos Amidi for his work in getting runtime.AddCleanup
It was implemented, polished, fell to GO 1.24. Finally, I would like to thank Carlos Amidi and Ian Lance Taylor for their replacement runtime.SetFinalizer
with runtime.AddCleanup
Throughout the GO 1.25 standard library.
Credits: Michael KNYSZEK
Photography by Bili R. Brezk is not earthquakes
This article is available on