业务中我们经常遇到需要进行手动回收的操作,虽然Go提供了defer操作可以用来手动回收,但是有些时候确实会出现一些case用户忘记手动回收,并且大量内存泄漏或者goroutine泄口的问题,而且只能通过线上工具进行事后定位!本文介绍一下 runtime.SetFinalizer
来解决对象回收释放资源的问题!本文只是根据简单的例子进行阐述,例子选择不一定的好!
介绍
runtime.SetFinalizer
是Go提供对象被GC回收时的一个注册函数,可以在对象被回收的时候回掉函数- 此方法类似于
JAVA
的finalize 方法和C++
的析构函数! - 当存在多层引用时,类似于A->B->C 这种关系的时候,是如何解决呢?
- Go函数内部原理介绍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| func SetFinalizer(obj interface{}, finalizer interface{}) { .... if etyp.kind&kindMask != kindPtr { throw("runtime.SetFinalizer: first argument is " + etyp.string() + ", not pointer") } if ftyp.kind&kindMask != kindFunc { throw("runtime.SetFinalizer: second argument is " + ftyp.string() + ", not a function") } createfing() systemstack(func() { if !addfinalizer(e.data, (*funcval)(f.data), nret, fint, ot) { throw("runtime.SetFinalizer: finalizer already set") } }) }
func createfing() { if fingCreate == 0 && atomic.Cas(&fingCreate, 0, 1) { go runfinq() } }
|
简单使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package main
import ( "fmt" "runtime" "time" )
type object int
func (o object) Ptr() *object { return &o }
var ( cacheData = make(map[string]*object, 1024) )
func deleteData(key string) { delete(cacheData, key) }
func setData(key string, v object) { data := v.Ptr() runtime.SetFinalizer(data, func(data *object) { fmt.Printf("runtime invoke Finalizer data: %d, time: %s\n", *data, time.Now().Format("15:04:05.000")) time.Sleep(time.Second) }) cacheData[key] = data }
func main() { setData("key1", 1) setData("key2", 2) setData("key3", 3)
deleteData("key1") deleteData("key2") deleteData("key3")
for x := 0; x < 5; x++ { fmt.Println("invoke runtime.GC()") runtime.GC() time.Sleep(time.Second) } }
|
- 并没有看出第二次才会GC掉,可能是系统在delele过程中触发过一次GC
- 可以看到GC后调用
Finalizer
函数是串行执行的!
日常使用注意点
- GC注册的
Finalizer
函数执行时间不适合过长! Finalizer
函数返回结果是系统会忽略,所以你返回error也无所谓,但是切记不可以panic,程序是无法recover的!- 如果对象在
Finalizer
函数再次被引用,是不会被再次回收调用Finalizer
函数的! - 当存在 A->B->C 的引用时,回收顺序是引用顺序,当回收A后,然后再回收B,然后再回收C,应该没啥问题吧
- 如果你对象被
goroutine
引用而分配到堆上,goroutine 又没办法关闭,导致你需要包装一层对象进行回收!例如下列例子,导致业务函数结束后忘记了Close,导致G泄漏,虽然注册了Finalizer函数,但是没有被回收!我就犯过这样的错误,不过在写测试用例的时候就发现了!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| type cacheData struct { name string dataLock sync.RWMutex data map[string]interface{} reporter func(data *cacheData)
closeOnce sync.Once done chan struct{} }
func NewCacheData(name string) *cacheData { data := &cacheData{ name: name, data: map[string]interface{}{}, reporter: func(data *cacheData) { log.Println("reporter") }, done: make(chan struct{}, 0), } data.init() runtime.SetFinalizer(data, (*cacheData).Close) return data }
func (c *cacheData) init() { go func() { c.reporter(c) t := time.NewTicker(time.Second) for { select { case <-c.done: t.Stop() return case <-t.C: c.reporter(c) } } }() }
func (c *cacheData) Close() { c.closeOnce.Do(func() { close(c.done) }) }
func BizFunc() { cache := NewCacheData("test")
cache.Set("k1", "v1")
}
func main() { BizFunc() for x := 0; x < 10; x++ { runtime.GC() log.Println("runtime.GC") time.Sleep(time.Second) } }
|
如何解决了?? 可以看 NewSafeCacheData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| package main
import ( "log" "runtime" "sync" "time" )
func init() { log.SetFlags(log.Ltime) } func (c *cacheData) Set(key string, v interface{}) { c.dataLock.Lock() defer c.dataLock.Unlock() c.data[key] = v }
type CacheData struct { *cacheData }
type cacheData struct { name string dataLock sync.RWMutex data map[string]interface{} reporter func(data *cacheData)
closeOnce sync.Once done chan struct{} }
func NewCacheData(name string) *cacheData { data := &cacheData{ name: name, data: map[string]interface{}{}, reporter: func(data *cacheData) { log.Println("reporter") }, done: make(chan struct{}, 0), } return data }
func NewSafeCacheData(name string) *CacheData { data := NewCacheData(name) data.init() result := &CacheData{ cacheData: data, } runtime.SetFinalizer(result, (*CacheData).Close) return result }
func (c *cacheData) init() { go func() { c.reporter(c) t := time.NewTicker(time.Second) for { select { case <-c.done: t.Stop() return case <-t.C: c.reporter(c) } } }() }
func (c *cacheData) Close() { c.closeOnce.Do(func() { close(c.done) }) }
func BizFunc() { cache := NewSafeCacheData("test")
cache.Set("k1", "v1")
}
func main() { BizFunc() for x := 0; x < 10; x++ { runtime.GC() log.Println("runtime.GC") time.Sleep(time.Second) } }
|