> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Go:  runtime.SetFinalizer 详解
- URL: https://huizhou92.com/go-runtimesetfinalizer/
- Published: 2024-09-04T11:33:11.000Z
- Updated: 2026-09-06T11:39:02.000Z
- Description: 本文介绍了Go语言中的runtime.SetFinalizer函数，探讨了如何在对象被垃圾回收之前执行清理操作。通过示例代码，展示了SetFinalizer的用法及其潜在问题，如内存泄漏和对象生命周期延长。文章还提到在实际应用中，SetFinalizer的使用较少，但在Go源码中有广泛应用，提供了对资源管理的深刻见解。
- Author: huizhou92
- Tags: tech, go, #lang-zh-cn

注意这是一篇旧文章，Golang可能会取消`runtime.SetFinalizer`，使用`runtime.AddCleanup` 替代。它解决了 `runtime.SetFinalizer` 一些痛点。具体内容可以参考我这篇文章

如果我们希望在一个对象被gc之前，做一些资源释放的工作，我们可以使用 `runtime.SetFinalizer`。就像函数返回之前执行defer释放资源一样。比如下面的代码:

> This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on [Medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you very much.

List1: example By `runtime.SetFinalizer`

```go

type MyStruct struct {  
    Name  string  
    Other *MyStruct  
}

func main() {  
    x := MyStruct{Name: "X"}  
    runtime.SetFinalizer(&x, func(x *MyStruct) {  
       fmt.Printf("Finalizer for %s is called\n", x.Name)  
    })  
    runtime.GC()  
    time.Sleep(1 * time.Second)  
    runtime.GC()  
}

```

[官方文档](https://pkg.go.dev/runtime?ref=huizhou92.com#SetFinalizer)中对SetFinalizer的一些解释，主要含义是对象可以关联一个SetFinalizer函数， 当`GC`检测到unreachable对象有关联的SetFinalizer函数时，会执行关联的SetFinalizer函数， 同时取消关联。 这样当下一次`GC`的时候，对象重新处于`unreachable`状态并且没有SetFinalizer关联， 就会被回收。

仔细看文档，还有几个需要注意的点：

- 即使程序正常结束或者发生错误， 但是在对象被 gc 选中并被回收之前，SetFinalizer 都不会执行， 所以不要在`SetFinalizer`中执行将内存中的内容flush到磁盘这种操作。
- `SetFinalizer` 最大的问题是延长了对象生命周期。在第一次回收时执行 Finalizer 函数，且目标对象重新变成可达状态，直到第二次才真正 “销毁”。这对于有大量对象分配的高并发算法，可能会造成很大麻烦
- 指针构成的 "循环引⽤" 加上 `runtime.SetFinalizer` 会导致内存泄露。

list 2： `runtime.SetFinalizer ` memory leak

```go
// MyStruct 是一个简单的结构体，包含一个指针字段。  
type MyStruct struct {  
    Name  string  
    Other *MyStruct  
}  
  
func main() {  
    x := MyStruct{Name: "X"}  
    y := MyStruct{Name: "Y"}  
  
    x.Other = &y  
    y.Other = &x  
    runtime.SetFinalizer(&x, func(x *MyStruct) {  
       fmt.Printf("Finalizer for %s is called\n", x.Name)  
    })  
    time.Sleep(time.Second)  
    runtime.GC()  
    time.Sleep(time.Second) 
    runtime.GC() 
}

```

x 永远不会被释放。正确的做法应该是， 在不需要使用 对象的时候，显式移除 Finalizer `runtime.SetFinalizer(&x, nil)`

## 实际应用

在业务代码中很少使用`runtime.SetFinalizer` （我没使用过）但是再Go源码中 有比较多的使用， 比如  
[net/http](https://go.googlesource.com/go/blob/aeac0b6cbfb42bc9c9301913a191bb09454d316a/src/net/fd%5Ffake.go?ref=huizhou92.com#L104)

```go
func (fd *netFD) setAddr(laddr, raddr Addr) {  
    fd.laddr = laddr  
    fd.raddr = raddr  
    runtime.SetFinalizer(fd, (*netFD).Close)  
}  
  
func (fd *netFD) Close() error {  
    if fd.fakeNetFD != nil {  
       return fd.fakeNetFD.Close()  
    }  
    runtime.SetFinalizer(fd, nil)  
    return fd.pfd.Close()  
}

```

[go-cache](https://github.com/patrickmn/go-cache/blob/46f407853014144407b6c2ec7ccc76bf67958d93/cache.go?ref=huizhou92.com#L1123)库提供了SetFinalizer的一种用法。

```Go
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`将会进行`GC`流程，首先执行`stopJanitor`函数，其作用是为内部的`stop channel`写入值，从而通知上一步的异步清理协程，使其退出。这样就实现了业务代码无感知的异步协程回收，是一种优雅的退出方式。

---

- [本文长期链接](https://huizhou92.com/go-runtimesetfinalizer)
- 如果您觉得我的博客对你有帮助，请通过 [RSS](https://huizhou92.com/sitemap.xml)订阅我。
- 或者在[X](https://x.com/@piaopiaopig?ref=huizhou92.com)上关注我。
- 如果您有[Medium](https://medium.huizhou92.com/?ref=huizhou92.com)账号，能给我个关注嘛？我的文章第一时间都会发布在Medium。