

Go: Finalizers - Vincent Blanchon - Medium
source link: https://medium.com/@blanchon.vincent/go-finalizers-786df8e17687
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go: Finalizers. ℹ️ This article is based on Go 1.12.
You have 2 free member-only stories left this month.


Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.
ℹ️ This article is based on Go 1.12.
Go runtime provides a method runtime.SetFinalizer
that allows developers to attach a function to a variable that will be called when the garbage collector sees this variable as garbage ready to be collected since it became unreachable. This feature is highly subject to debate and this article does not aim to participate in it, but rather to explain the implementation of the method.
No guarantees
Let’s take an example of a program that uses finalizers:
This program will create, in a loop, three instances of a struct and attach a finalizer to it. Then, the garbage collector will be called to collect the instances created previously. Running this program will give us the following output:
31
37
47
As we can see, finalizers have not been called. The documentation of the runtime could explain this point:
The finalizer is scheduled to run at some arbitrary time after the program can no longer reach the object to which obj points. There is no guarantee that finalizers will run before a program exits, so typically they are useful only for releasing non-memory resources associated with an object during a long-running program
Runtime does not provide any guarantee about the delay before the finalizer will be called. Let’s try to modify our program by adding a one second sleep after calling the garbage collector:
31
37
47
foo 1 has been garbage collected
foo 0 has been garbage collected
Now our finalizers have been called. However, one is missing. Our finalizers are tied to the Go garbage collector and the way it will collect and clean the data will impact the call to the finalizers.
Workflow
The previous example could let us think that Go calls our finalizers just before freeing the memory made by our structs.
Let’s see exactly what happens with more allocations:
One million structs with finalizers are created, here is the output:
Allocation: 0.090862 Mb, Number of allocation: 137
Allocation: 31.107506 Mb, Number of allocation: 2390078
Allocation: 110.052666 Mb, Number of allocation: 4472742
Let’s try again without finalizers:
Allocation: 0.090694 Mb, Number of allocation: 136
Allocation: 18.129814 Mb, Number of allocation: 1390078
Allocation: 0.094451 Mb, Number of allocation: 154
It seems that nothing has been cleaned in memory despite the garbage collector being triggered and the finalizers run. Let’s go back to the runtime documentation in order to understand this behavior:
When the garbage collector finds an unreachable block with an associated finalizer, it clears the association and runs finalizer(obj) in a separate goroutine. This makes obj reachable again, but now without an associated finalizer. Assuming that SetFinalizer is not called again, the next time the garbage collector sees that obj is unreachable, it will free obj.
As we can see, the finalizers are first removed, and the memory will be released on the next cycle. Let’s run our first example again with two forced garbage collections:
Allocation: 0.090862 Mb, Number of allocation: 137
Allocation: 31.107506 Mb, Number of allocation: 2390078
Allocation: 110.052666 Mb, Number of allocation: 4472742
Allocation: 0.099220 Mb, Number of allocation: 166
We can clearly see that the second run will actually clean the data. The finalizers could therefore slightly affect the performance and memory usage.
Performance
The finalizers run one after another as explained in the documentation:
A single goroutine runs all finalizers for a program, sequentially. If a finalizer must run for a long time, it should do so by starting a new goroutine.
Only one goroutine will run the finalizers and any heavy task should create a new goroutine. When the finalizers run, the garbage collector does not stop the world and run in concurrency; it should therefore not affect your application performance.
Also, Go provides a way to remove a finalizer if it is not needed anymore:
runtime.SetFinalizer(p, nil)
It allows us to dynamically remove them depending on our usage.
Application usage
Internally, Go uses the finalizers in the net
, net/http
packages to ensures the files previously opened are closed, and in the os
package to ensure the created processes are released well. Here is an example from the os
package:
func newProcess(pid int, handle uintptr) *Process {
p := &Process{Pid: pid, handle: handle}
runtime.SetFinalizer(p, (*Process).Release)
return p
}
When the process is actually released, the finalizer will be removed:
func (p *Process) release() error {
// NOOP for unix.
p.Pid = -1
// no need for a finalizer anymore
runtime.SetFinalizer(p, nil)
return nil
}
Go also uses finalizers in the tests to ensure specific expected actions are done at the garbage collection. For example, the sync
package uses the finalizers to test if the pool is cleaned during the garbage collection cycle.
</div
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK