返回列表 发布新帖
查看: 12|回复: 0

Go 1.24 中的弱指针包 weak 使用介绍

发表于 4 天前 | 查看全部 |阅读模式

这里或许是互联网从业者的最后一片净土,随客社区期待您的加入!

您需要 登录 才可以下载或查看,没有账号?立即注册

×
在 Go 语言中,“弱指针”是一种不会阻止垃圾回收器(GC)回收目标对象的引用。当一个对象只剩下弱指针引用,而没有任何强引用时,GC 会将该对象标记为不可达并回收。此后,所有指向该对象的弱指针会自动变为 nil。
简而言之:弱指针不会影响对象的生命周期,因此在使用前必须检查其是否为 nil。

Go 1.24 新增 weak 包
Go 1.24 正式引入了标准库中的 weak 包,提供了简洁的 API 来创建和访问弱指针。
  1. import "weak"

  2. type MyStruct struct {
  3.     Data string
  4. }

  5. func main() {
  6.     obj := &MyStruct{Data: "example"}
  7.     wp := weak.Make(obj) // 创建弱指针
  8.     val := wp.Value()    // 获取强引用或 nil

  9.     if val != nil {
  10.         fmt.Println(val.Data)
  11.     } else {
  12.         fmt.Println("对象已被垃圾回收")
  13.     }
  14. }
复制代码
weak.Make(obj) 创建了一个指向 obj 的弱指针。调用 wp.Value() 时,如果对象仍然存活,则返回一个强引用;否则返回 nil。

测试弱指针行为
我们可以通过移除强引用并主动触发 GC 来观察弱指针的行为:
  1. import (
  2.     "fmt"
  3.     "runtime"
  4.     "weak"
  5. )

  6. type MyStruct struct {
  7.     Data string
  8. }

  9. func main() {
  10.     obj := &MyStruct{Data: "test"}
  11.     wp := weak.Make(obj)

  12.     obj = nil        // 移除强引用
  13.     runtime.GC()     // 触发 GC

  14.     if wp.Value() == nil {
  15.         fmt.Println("对象已被垃圾回收")
  16.     } else {
  17.         fmt.Println("对象仍然存活")
  18.     }
  19. }
复制代码

强引用 vs 弱引用
特性强引用 (*T)弱引用 (weak.Pointer[T])
是否阻止 GC
空值nilnil(被回收或未赋值)
访问方式直接使用调用 Value() 获取强引用

示例 1:弱指针实现轻量级缓存
使用弱指针最典型的场景之一是缓存:在不强制条目常驻内存的情况下临时保存数据。
  1. package main

  2. import (
  3.     "fmt"
  4.     "runtime"
  5.     "sync"
  6.     "weak"
  7. )

  8. type User struct {
  9.     Name string
  10. }

  11. var cache sync.Map // map[int]weak.Pointer[*User]

  12. func GetUser(id int) *User {
  13.     if wp, ok := cache.Load(id); ok {
  14.         if u := wp.(weak.Pointer[User]).Value(); u != nil {
  15.             fmt.Println("cache hit")
  16.             return u
  17.         }
  18.     }

  19.     u := &User{Name: fmt.Sprintf("user-%d", id)}
  20.     cache.Store(id, weak.Make(u))
  21.     fmt.Println("load from DB")
  22.     return u
  23. }

  24. func main() {
  25.     u := GetUser(1) // 第一次加载
  26.     fmt.Println(u.Name)

  27.     runtime.GC() // 即使 GC,因 main 持有强引用,User 仍在

  28.     u = nil      // 移除强引用
  29.     runtime.GC() // 再次 GC,User 可能被回收

  30.     _ = GetUser(1) // 若被回收,会重新加载
  31. }
复制代码
输出示例:
  1. load from DB
  2. user-1
  3. load from DB
复制代码

为什么使用弱指针?
适用场景包括:
  • 缓存系统:不阻止对象被 GC 回收,节省内存;
  • 观察者模式:不影响观察者的生命周期;
  • 规范化(Canonicalization):避免重复创建相同内容的对象;
  • 依赖图结构:防止结构间产生强引用循环,导致内存泄漏。


使用弱指针的注意事项
  • 始终检查是否为 nil:GC 可在任意时间回收对象;
  • 避免形成循环依赖:否则弱引用会变成实际的强引用链;
  • 性能权衡:频繁使用 Value() 访问弱引用可能影响性能,尤其在短时间内频繁创建、销毁对象的场景中。


示例 2:强引用的普通用法
  1. package main

  2. import (
  3.     "fmt"
  4.     "runtime"
  5. )

  6. type Session struct {
  7.     ID string
  8. }

  9. func main() {
  10.     s := new(Session) // 与 &Session{} 等价
  11.     s.ID = "abc123"

  12.     fmt.Println("strong ref alive:", s.ID)

  13.     s = nil        // 移除强引用
  14.     runtime.GC()   // 手动触发 GC(仅建议)

  15.     fmt.Println("done")
  16. }
复制代码
只要变量 s 仍然存在,GC 就不会回收 Session 对象。只有当它不再从任何根对象(栈、全局变量等)可达时,才会被释放。

回收机制:强指针何时释放?
Go 的 GC 使用可达性分析(Mark and Sweep)
  • 从栈、全局变量等根对象出发;
  • 所有可达对象视为存活;
  • 其余视为不可达,进入回收阶段;
  • 不使用引用计数机制,变量数量、重复引用不影响回收;
  • runtime.GC() 仅是建议触发,实际时机由调度器决定。

简而言之:只要有强引用链存在,对象就不会被 GC 回收;一旦断开,下一轮 GC 就可能释放它。

总结
Go 1.24 中引入的 weak 包,提供了一个原生、安全的方式实现弱引用。它尤其适用于缓存、资源观察、依赖图等场景,可帮助我们更好地控制内存使用,并避免不必要的内存泄漏。
当你希望对象在“用完即走”的同时还能保留轻量级访问能力,弱指针是你的理想选择。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2001-2025 Suike Tech All Rights Reserved. 随客交流社区 (备案号:津ICP备19010126号) |Processed in 0.107924 second(s), 9 queries , Gzip On, MemCached On.
关灯 在本版发帖返回顶部
快速回复 返回顶部 返回列表