Go 1.24 中的弱指针包 weak 使用介绍
在 Go 语言中,“弱指针”是一种不会阻止垃圾回收器(GC)回收目标对象的引用。当一个对象只剩下弱指针引用,而没有任何强引用时,GC 会将该对象标记为不可达并回收。此后,所有指向该对象的弱指针会自动变为 nil。简而言之:弱指针不会影响对象的生命周期,因此在使用前必须检查其是否为 nil。Go 1.24 新增 weak 包
Go 1.24 正式引入了标准库中的 weak 包,提供了简洁的 API 来创建和访问弱指针。import "weak"
type MyStruct struct {
Data string
}
func main() {
obj := &MyStruct{Data: "example"}
wp := weak.Make(obj) // 创建弱指针
val := wp.Value() // 获取强引用或 nil
if val != nil {
fmt.Println(val.Data)
} else {
fmt.Println("对象已被垃圾回收")
}
}
weak.Make(obj) 创建了一个指向 obj 的弱指针。调用 wp.Value() 时,如果对象仍然存活,则返回一个强引用;否则返回 nil。
测试弱指针行为
我们可以通过移除强引用并主动触发 GC 来观察弱指针的行为:import (
"fmt"
"runtime"
"weak"
)
type MyStruct struct {
Data string
}
func main() {
obj := &MyStruct{Data: "test"}
wp := weak.Make(obj)
obj = nil // 移除强引用
runtime.GC() // 触发 GC
if wp.Value() == nil {
fmt.Println("对象已被垃圾回收")
} else {
fmt.Println("对象仍然存活")
}
}
强引用 vs 弱引用
特性强引用 (*T)弱引用 (weak.Pointer)
是否阻止 GC是否
空值nilnil(被回收或未赋值)
访问方式直接使用调用 Value() 获取强引用
示例 1:弱指针实现轻量级缓存使用弱指针最典型的场景之一是缓存:在不强制条目常驻内存的情况下临时保存数据。package main
import (
"fmt"
"runtime"
"sync"
"weak"
)
type User struct {
Name string
}
var cache sync.Map // mapweak.Pointer[*User]
func GetUser(id int) *User {
if wp, ok := cache.Load(id); ok {
if u := wp.(weak.Pointer).Value(); u != nil {
fmt.Println("cache hit")
return u
}
}
u := &User{Name: fmt.Sprintf("user-%d", id)}
cache.Store(id, weak.Make(u))
fmt.Println("load from DB")
return u
}
func main() {
u := GetUser(1) // 第一次加载
fmt.Println(u.Name)
runtime.GC() // 即使 GC,因 main 持有强引用,User 仍在
u = nil // 移除强引用
runtime.GC() // 再次 GC,User 可能被回收
_ = GetUser(1) // 若被回收,会重新加载
}
输出示例:load from DB
user-1
load from DB
为什么使用弱指针?适用场景包括:
[*]缓存系统:不阻止对象被 GC 回收,节省内存;
[*]观察者模式:不影响观察者的生命周期;
[*]规范化(Canonicalization):避免重复创建相同内容的对象;
[*]依赖图结构:防止结构间产生强引用循环,导致内存泄漏。
使用弱指针的注意事项
[*]始终检查是否为 nil:GC 可在任意时间回收对象;
[*]避免形成循环依赖:否则弱引用会变成实际的强引用链;
[*]性能权衡:频繁使用 Value() 访问弱引用可能影响性能,尤其在短时间内频繁创建、销毁对象的场景中。
示例 2:强引用的普通用法
package main
import (
"fmt"
"runtime"
)
type Session struct {
ID string
}
func main() {
s := new(Session) // 与 &Session{} 等价
s.ID = "abc123"
fmt.Println("strong ref alive:", s.ID)
s = nil // 移除强引用
runtime.GC() // 手动触发 GC(仅建议)
fmt.Println("done")
}
只要变量 s 仍然存在,GC 就不会回收 Session 对象。只有当它不再从任何根对象(栈、全局变量等)可达时,才会被释放。
回收机制:强指针何时释放?Go 的 GC 使用可达性分析(Mark and Sweep):
[*]从栈、全局变量等根对象出发;
[*]所有可达对象视为存活;
[*]其余视为不可达,进入回收阶段;
[*]不使用引用计数机制,变量数量、重复引用不影响回收;
[*]runtime.GC() 仅是建议触发,实际时机由调度器决定。
简而言之:只要有强引用链存在,对象就不会被 GC 回收;一旦断开,下一轮 GC 就可能释放它。
总结Go 1.24 中引入的 weak 包,提供了一个原生、安全的方式实现弱引用。它尤其适用于缓存、资源观察、依赖图等场景,可帮助我们更好地控制内存使用,并避免不必要的内存泄漏。当你希望对象在“用完即走”的同时还能保留轻量级访问能力,弱指针是你的理想选择。
页:
[1]