业务中我们经常遇到需要进行手动回收的操作,虽然Go提供了defer操作可以用来手动回收,但是有些时候确实会出现一些case用户忘记手动回收,并且大量内存泄漏或者goroutine泄口的问题,而且只能通过线上工具进行事后定位!本文介绍一下 runtime.SetFinalizer 来解决对象回收释放资源的问题!本文只是根据简单的例子进行阐述,例子选择不一定的好!
介绍
- runtime.SetFinalizer是Go提供对象被GC回收时的一个注册函数,可以在对象被回收的时候回掉函数
- 此方法类似于JAVA的finalize 方法和C++的析构函数!
- 当存在多层引用时,类似于A->B->C 这种关系的时候,是如何解决呢?
- Go函数内部原理介绍
| 12
 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()
 }
 }
 
 
 
 
 
 | 
简单使用
| 12
 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函数,但是没有被回收!我就犯过这样的错误,不过在写测试用例的时候就发现了!!
| 12
 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
| 12
 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)
 }
 }
 
 |