Go如何巧妙使用runtime.SetFinalizer

runtime.SetFinalizer

func SetFinalizer(obj interface{}, finalizer interface{})

SetFinalizer sets the finalizer associated with obj to the provided finalizer function. 
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

 

上面是官方文档对SetFinalizer的一些解释,主要含义是对象可以关联一个SetFinalizer函数, 当gc检测到unreachable对象有关联的SetFinalizer函数时,会执行关联的SetFinalizer函数, 同时取消关联。 这样当下一次gc的时候,对象重新处于unreachable状态并且没有SetFinalizer关联, 就会被回收。

仔细看文档,还有几个需要注意的点:

* 即使程序正常结束或者发生错误, 但是在对象被 gc 选中并被回收之前,SetFinalizer 都不会执行, 所以不要在SetFinalizer中执行将内存中的内容flush到磁盘这种操作

* SetFinalizer 最大的问题是延长了对象生命周期。在第一次回收时执行 Finalizer 函数,且目标对象重新变成可达状态,直到第二次才真正 “销毁”。这对于有大量对象分配的高并发算法,可能会造成很大麻烦

* 指针构成的 "循环引⽤" 加上 runtime.SetFinalizer 会导致内存泄露

 

package main

import (
	"log"
	"runtime"
	"time"
)

type test int

func findRoad(t *test) {
	// 一般在这里进行资源回收
	log.Println("test:", *t)
}

func entry() {
	var rd test = test(1111)
	r := &rd
	// 解除绑定并执行对应函数下一次gc在进行清理
	runtime.SetFinalizer(r, findRoad)
}

func main() {
	entry()
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		runtime.GC()
	}
}

  

函数原型

func SetFinalizer(x, f interface{})

  

SetFinalizer将x的终止器设置为f。当垃圾收集器发现一个不能接触的(即引用计数为零,程序中不能再直接或间接访问该对象)具有终止器的块时,它会清理该关联(对象到终止器)并在独立go程调用f(x)。这使x再次可以接触,但没有了绑定的终止器。如果SetFinalizer没有被再次调用,下一次垃圾收集器将视x为不可接触的,并释放x。

SetFinalizer(x, nil)会清理任何绑定到x的终止器。

参数x必须是一个指向通过new申请的对象的指针,或者通过对复合字面值取址得到的指针。参数f必须是一个函数,它接受单个可以直接用x类型值赋值的参数,也可以有任意个被忽略的返回值。如果这两条任一条不被满足,本函数就会中断程序。

终止器会按依赖顺序执行:如果A指向B,两者都有终止器,且它们无法从其它方面接触,只有A的终止器执行;A被释放后,B的终止器就可以执行。如果一个循环结构包含一个具有终止器的块,该循环不能保证会被当垃圾收集,终止器也不能保证会执行;因为没有尊重依赖关系的顺序。

x的终止器会在x变为不可接触之后的任意时间被调度执行。不保证终止器会在程序退出前执行,因此一般终止器只用于在长期运行的程序中释放关联到某对象的非内存资源。例如,当一个程序丢弃一个os.File对象时没有调用其Close方法,该os.File对象可以使用终止器去关闭对应的操作系统文件描述符。但依靠终止器去刷新内存中的I/O缓冲如bufio.Writer是错误的,因为缓冲不会在程序退出时被刷新。

如果*x的大小为0字节,不保证终止器会执行。

一个程序会有单独一个go程顺序执行所有的终止器。如果一个终止器必须运行较长时间,它应该在内部另开go程执行该任务。

 

 


posted @ 2020-05-16 17:29  Binb  阅读(3038)  评论(0编辑  收藏  举报