mrkong 发表于 3 天前

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]
查看完整版本: Go 1.24 中的弱指针包 weak 使用介绍