runtime.SetFinalizer使用学习

我们知道Go是自带GC的语言,在代码中调用runtime.GC()可以显式触发GC,当内存资源不足时调用可以在该时间点释放内存,但此时,由于STW机制的存在,程序性能可能短暂下降。runtime.SetFinalizer可以在内存中对象被回收前,指定一些操作。

用法介绍

函数定义如下:

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

阅读官方的注释,有几点需要注意:

1.obj必须是对象指针。
2.SetFinalizer使原本对象的回收延长到了两步,第一步是解除finalizer和obj对象的关联,另起一个协程执行finalizer。第二步gc时才真正回收obj对象。这造成了对象生命周期的延长,对于大量对象分配的高并发场景需要引起注意。
3.finalizer的执行顺序具有依赖关系。如果A指向B,两者都设置了finalizer,则如果A和B均不可达,则在GC时首先执行A的finalizer,然后回收A。之后才能执行B的finalizer。
4.禁止指针循环引用和SetFinalizer同时使用。考虑到第3点,如果多个指针构成循环引用,则无法确定finalizer的依赖关系,因而无法执行SetFinalizer,目标对象不能变成不可达状态,造成内存无法回收。

应用实例

go-cache库提供了SetFinalizer的一种用法。

定义cache为包内部结构体,成员变量有实际存储数据的items,再在Cache包装cache,作为导出结构体

type Cache struct {
	*cache
}

type cache struct {
	defaultExpiration time.Duration
	items             map[string]Item
	mu                sync.RWMutex
	onEvicted         func(string, interface{})
	janitor           *janitor
}

初始化一个带有定期清理间隔时长的cache如下:

func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
	items := make(map[string]Item)
	return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
}

func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
	c := newCache(de, m)
	C := &Cache{c}
	if ci > 0 {
		runJanitor(c, ci)
		runtime.SetFinalizer(C, stopJanitor)
	}
	return C
}

func runJanitor(c *cache, ci time.Duration) {
	j := &janitor{
		Interval: ci,
		stop:     make(chan bool),
	}
	c.janitor = j
	go j.Run(c)
}

func stopJanitor(c *Cache) {
	c.janitor.stop <- true
}

func (j *janitor) Run(c *cache) {
	ticker := time.NewTicker(j.Interval)
	for {
		select {
		case <-ticker.C:
			c.DeleteExpired()
		case <-j.stop:
			ticker.Stop()
			return
		}
	}
}

newCacheWithJanitor在ci参数大于0时,将开启后台协程,通过ticker定期清理过期缓存。一旦从stop chan中读到值,则异步协程退出。

stopJanitor为指向Cache的指针C定义了finalizer函数stopJanitor。一旦我们在业务代码中不再有指向Cache的引用时,C将会进行CG流程,首先执行stopJanitor函数,其作用是为内部的stop channel写入值,从而通知上一步的异步清理协程,使其退出。这样就实现了业务代码无感知的异步协程回收,是一种优雅的退出方式。

posted @   g2012  阅读(428)  评论(0编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
历史上的今天:
2020-11-01 MySQL事务、隔离级别与锁
点击右上角即可分享
微信分享提示