weak 包详解
概述
weak 包提供了安全地弱引用内存的方式,即不会阻止其被回收。
主要用途:
- 实现缓存(caches)
- 规范化映射(canonicalization maps)
- 绑定不同值的生命周期
- 弱键映射(weak-keyed maps)
- 避免内存泄漏
核心概念:
- 弱指针(Weak Pointer):不会阻止对象被垃圾回收的指针
- 可达性(Reachability):仅被弱指针引用的对象被认为不可达
- 垃圾回收(Garbage Collection):当对象不可达时,弱指针的 Value 方法可能返回 nil
Go 版本要求:Go 1.21+
包导入
import "weak"
类型详解(按 A-Z 分层归类)
P
Pointer
type Pointer[T any] struct {
// 包含导出或未导出的字段
}
作用:指向类型 T 值的弱指针
说明:
- 与普通指针一样,Pointer 可以引用对象的任何部分(如结构体字段或数组元素)
- 仅被弱指针引用的对象被认为不可达
- 一旦对象变得不可达,
Pointer.Value可能返回 nil - 两个 Pointer 值比较相等,当且仅当用于创建它们的指针比较相等
- 即使对象被回收,这个性质也会保持
- 如果多个弱指针指向同一对象的不同偏移(如不同字段),它们不会比较相等
- 弱指针映射到对象和对象内的偏移,而不是简单的地址
示例:
// 创建弱指针
type MyStruct struct {
Value int
}
obj := &MyStruct{Value: 42}
weakPtr := weak.Make(obj)
// 获取原始指针
if v := weakPtr.Value(); v != nil {
fmt.Println(v.Value) // 42
}
// 对象被回收后
obj = nil
runtime.GC()
if v := weakPtr.Value(); v == nil {
fmt.Println("Object was reclaimed")
}
// 比较弱指针
obj1 := &MyStruct{Value: 1}
obj2 := &MyStruct{Value: 2}
wp1 := weak.Make(obj1)
wp2 := weak.Make(obj2)
wp3 := weak.Make(obj1)
fmt.Println(wp1 == wp2) // false
fmt.Println(wp1 == wp3) // true
函数详解(按 A-Z 分层归类)
M
Make
func Make[T any](ptr *T) Pointer[T]
作用:从指向类型 T 的指针创建弱指针
参数说明:
ptr:要创建弱指针的普通指针
返回值:
- 弱指针
说明:
- 使用 nil 指针调用 Make 返回一个 Pointer.Value 始终返回 nil 的弱指针
- Pointer 的零值行为如同通过传递 nil 给 Make 创建
- 零值弱指针与通过 nil 创建的弱指针比较相等
示例:
// 基本用法
type Data struct {
Name string
}
obj := &Data{Name: "test"}
weakPtr := weak.Make(obj)
// 使用弱指针
if v := weakPtr.Value(); v != nil {
fmt.Println(v.Name) // "test"
}
// nil 指针
var nilPtr *Data
nilWeak := weak.Make(nilPtr)
fmt.Println(nilWeak.Value() == nil) // true
// 零值弱指针
var zeroWeak weak.Pointer[Data]
fmt.Println(zeroWeak.Value() == nil) // true
fmt.Println(zeroWeak == nilWeak) // true
// 泛型使用
intObj := 42
intWeak := weak.Make(&intObj)
if v := intWeak.Value(); v != nil {
fmt.Println(*v) // 42
}
Pointer 方法详解(按 A-Z 分层归类)
V
Value
func (p Pointer[T]) Value() *T
作用:返回用于创建弱指针的原始指针
返回值:
- 原始指针
- 如果原始指针指向的值被垃圾回收,返回 nil
说明:
- 不保证最终返回 nil(即使对象不再被引用)
- 一旦对象变得不可达,可能立即返回 nil
- 存储在全局变量中的值或可以从全局变量追踪到的值是可达的
- 函数参数或接收者可能在函数最后一次提及它时变得不可达
- 为确保 Pointer.Value 不返回 nil,在对象必须保持可达的最后一点之后,将指针传递给
runtime.KeepAlive - 如果弱指针指向带有终结器(finalizer)的对象,当对象的终结器被排队执行时,Value 将返回 nil
示例:
// 基本用法
obj := &MyStruct{Value: 42}
weakPtr := weak.Make(obj)
// 检查对象是否仍然可达
if v := weakPtr.Value(); v != nil {
fmt.Println("Object still alive:", v.Value)
} else {
fmt.Println("Object was reclaimed")
}
// 使用 runtime.KeepAlive 确保对象存活
func processWeak(wp weak.Pointer[MyStruct]) {
v := wp.Value()
if v != nil {
// 使用 v
fmt.Println(v.Value)
}
// 确保 obj 在整个函数执行期间保持可达
runtime.KeepAlive(v)
}
// 对象被回收后
obj = nil
runtime.GC()
v := weakPtr.Value()
fmt.Println(v == nil) // true
典型示例
1. 实现简单缓存
package main
import (
"fmt"
"runtime"
"sync"
"weak"
)
type CacheItem struct {
Key string
Value string
}
type Cache struct {
mu sync.RWMutex
data map[string]weak.Pointer[CacheItem]
}
func NewCache() *Cache {
return &Cache{
data: make(map[string]weak.Pointer[CacheItem]),
}
}
func (c *Cache) Get(key string) *CacheItem {
c.mu.RLock()
defer c.mu.RUnlock()
if wp, ok := c.data[key]; ok {
if item := wp.Value(); item != nil {
return item
}
// 对象已被回收,从 map 中移除
delete(c.data, key)
}
return nil
}
func (c *Cache) Set(key string, item *CacheItem) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = weak.Make(item)
}
func main() {
cache := NewCache()
// 添加缓存项
item := &CacheItem{Key: "user:1", Value: "Alice"}
cache.Set("user:1", item)
// 获取缓存项
if cached := cache.Get("user:1"); cached != nil {
fmt.Printf("Cached: %+v\n", cached)
}
// 释放引用
item = nil
runtime.GC()
// 缓存项可能已被回收
if cached := cache.Get("user:1"); cached != nil {
fmt.Printf("Still cached: %+v\n", cached)
} else {
fmt.Println("Cache item was reclaimed")
}
}
2. 规范化映射
package main
import (
"fmt"
"sync"
"weak"
)
type Canonicalizer[T comparable] struct {
mu sync.Mutex
data map[T]weak.Pointer[T]
}
func NewCanonicalizer[T comparable]() *Canonicalizer[T] {
return &Canonicalizer[T]{
data: make(map[T]weak.Pointer[T]),
}
}
func (c *Canonicalizer[T]) Canonicalize(value T) *T {
c.mu.Lock()
defer c.mu.Unlock()
// 检查是否已存在
if wp, ok := c.data[value]; ok {
if existing := wp.Value(); existing != nil {
return existing
}
// 对象已被回收,清理
delete(c.data, value)
}
// 存储新值
ptr := &value
c.data[value] = weak.Make(ptr)
return ptr
}
func main() {
canon := NewCanonicalizer[string]()
// 规范化字符串
s1 := canon.Canonicalize("hello")
s2 := canon.Canonicalize("hello")
s3 := canon.Canonicalize("world")
fmt.Printf("s1 == s2: %v\n", s1 == s2) // true
fmt.Printf("s1 == s3: %v\n", s1 == s3) // false
// 内存优化:相同的值共享存储
fmt.Printf("s1 address: %p\n", s1)
fmt.Printf("s2 address: %p\n", s2) // 相同地址
}
3. 弱键映射
package main
import (
"fmt"
"runtime"
"sync"
"weak"
)
type WeakKeyMap[K comparable, V any] struct {
mu sync.Mutex
data map[*K]weak.Pointer[entry[K, V]]
}
type entry[K comparable, V any] struct {
key K
value V
}
func NewWeakKeyMap[K comparable, V any]() *WeakKeyMap[K, V] {
return &WeakKeyMap[K, V]{
data: make(map[*K]weak.Pointer[entry[K, V]]),
}
}
func (m *WeakKeyMap[K, V]) Set(key *K, value V) {
m.mu.Lock()
defer m.mu.Unlock()
e := &entry[K, V]{key: *key, value: value}
m.data[key] = weak.Make(e)
}
func (m *WeakKeyMap[K, V]) Get(key *K) (V, bool) {
m.mu.Lock()
defer m.mu.Unlock()
var zero V
if wp, ok := m.data[key]; ok {
if e := wp.Value(); e != nil {
return e.value, true
}
// 对象已被回收
delete(m.data, key)
}
return zero, false
}
func main() {
m := NewWeakKeyMap[string, int]()
key := "test"
m.Set(&key, 42)
if val, ok := m.Get(&key); ok {
fmt.Printf("Value: %d\n", val)
}
// key 离开作用域后,条目可能被回收
key = ""
runtime.GC()
}
4. 绑定对象生命周期
package main
import (
"fmt"
"runtime"
"weak"
)
type Resource struct {
Name string
}
type ResourceHandle struct {
resource weak.Pointer[Resource]
}
func NewResourceHandle(r *Resource) *ResourceHandle {
return &ResourceHandle{
resource: weak.Make(r),
}
}
func (h *ResourceHandle) Use() error {
r := h.resource.Value()
if r == nil {
return fmt.Errorf("resource has been reclaimed")
}
fmt.Printf("Using resource: %s\n", r.Name)
return nil
}
func main() {
res := &Resource{Name: "Database Connection"}
handle := NewResourceHandle(res)
// 使用资源
if err := handle.Use(); err != nil {
fmt.Println(err)
}
// 释放资源
res = nil
runtime.GC()
// 资源已被回收
if err := handle.Use(); err != nil {
fmt.Println(err) // resource has been reclaimed
}
}
5. 缓存网络响应
package main
import (
"fmt"
"runtime"
"sync"
"time"
"weak"
)
type Response struct {
URL string
Data []byte
Expires time.Time
}
type ResponseCache struct {
mu sync.RWMutex
data map[string]weak.Pointer[Response]
}
func NewResponseCache() *ResponseCache {
return &ResponseCache{
data: make(map[string]weak.Pointer[Response]),
}
}
func (c *ResponseCache) Get(url string) *Response {
c.mu.RLock()
defer c.mu.RUnlock()
if wp, ok := c.data[url]; ok {
if resp := wp.Value(); resp != nil {
if time.Now().Before(resp.Expires) {
return resp
}
}
// 过期或被回收
delete(c.data, url)
}
return nil
}
func (c *ResponseCache) Set(url string, resp *Response) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[url] = weak.Make(resp)
}
func main() {
cache := NewResponseCache()
// 缓存响应
resp := &Response{
URL: "https://api.example.com/data",
Data: []byte(`{"key": "value"}`),
Expires: time.Now().Add(time.Hour),
}
cache.Set(resp.URL, resp)
// 获取缓存
if cached := cache.Get(resp.URL); cached != nil {
fmt.Printf("Cache hit: %s\n", cached.URL)
}
// 释放引用
resp = nil
runtime.GC()
// 可能已被回收
if cached := cache.Get("https://api.example.com/data"); cached != nil {
fmt.Printf("Still cached\n")
} else {
fmt.Println("Cache miss")
}
}
6. 避免循环引用
package main
import (
"fmt"
"runtime"
"weak"
)
type Parent struct {
Name string
Children []*Child
}
type Child struct {
Name string
parent weak.Pointer[Parent]
}
func NewChild(name string, parent *Parent) *Child {
return &Child{
Name: name,
parent: weak.Make(parent),
}
}
func (c *Child) GetParent() *Parent {
return c.parent.Value()
}
func main() {
parent := &Parent{Name: "Parent"}
child := NewChild("Child", parent)
parent.Children = append(parent.Children, child)
// 访问父节点
if p := child.GetParent(); p != nil {
fmt.Printf("Child's parent: %s\n", p.Name)
}
// 释放父节点
parent = nil
runtime.GC()
// 父节点可能被回收
if p := child.GetParent(); p != nil {
fmt.Printf("Parent still exists: %s\n", p.Name)
} else {
fmt.Println("Parent was reclaimed")
}
}
7. 实现观察者模式
package main
import (
"fmt"
"runtime"
"sync"
"weak"
)
type Observer interface {
Notify(event string)
}
type Subject struct {
mu sync.Mutex
observers []weak.Pointer[Observer]
}
func NewSubject() *Subject {
return &Subject{
observers: make([]weak.Pointer[Observer], 0),
}
}
func (s *Subject) AddObserver(o Observer) {
s.mu.Lock()
defer s.mu.Unlock()
s.observers = append(s.observers, weak.Make(o))
}
func (s *Subject) NotifyAll(event string) {
s.mu.Lock()
defer s.mu.Unlock()
// 清理并通知
active := make([]weak.Pointer[Observer], 0, len(s.observers))
for _, wp := range s.observers {
if o := wp.Value(); o != nil {
o.Notify(event)
active = append(active, wp)
}
}
s.observers = active
}
type ConcreteObserver struct {
Name string
}
func (o *ConcreteObserver) Notify(event string) {
fmt.Printf("%s received event: %s\n", o.Name, event)
}
func main() {
subject := NewSubject()
obs1 := &ConcreteObserver{Name: "Observer 1"}
obs2 := &ConcreteObserver{Name: "Observer 2"}
subject.AddObserver(obs1)
subject.AddObserver(obs2)
// 通知所有观察者
subject.NotifyAll("Event 1")
// 释放一个观察者
obs1 = nil
runtime.GC()
// 只通知存活的观察者
subject.NotifyAll("Event 2")
}
8. 比较弱指针
package main
import (
"fmt"
"weak"
)
type Data struct {
Value int
}
func main() {
// 相同对象的弱指针比较相等
obj := &Data{Value: 42}
wp1 := weak.Make(obj)
wp2 := weak.Make(obj)
fmt.Println(wp1 == wp2) // true
// 不同对象的弱指针比较不相等
obj2 := &Data{Value: 42}
wp3 := weak.Make(obj2)
fmt.Println(wp1 == wp3) // false
// 同一对象不同字段的弱指针比较不相等
type Pair struct {
A, B int
}
pair := &Pair{A: 1, B: 2}
wpA := weak.Make(&pair.A)
wpB := weak.Make(&pair.B)
fmt.Println(wpA == wpB) // false
// nil 弱指针
var nilWp weak.Pointer[Data]
nilWp2 := weak.Make[Data](nil)
fmt.Println(nilWp == nilWp2) // true
fmt.Println(nilWp.Value() == nil) // true
}
9. 与 finalizer 配合使用
package main
import (
"fmt"
"runtime"
"weak"
)
type Resource struct {
Name string
}
func main() {
res := &Resource{Name: "Test Resource"}
// 设置终结器
runtime.SetFinalizer(res, func(r *Resource) {
fmt.Printf("Finalizing: %s\n", r.Name)
})
// 创建弱指针
wp := weak.Make(res)
// 释放强引用
res = nil
// 触发垃圾回收
runtime.GC()
// 弱指针应该返回 nil
if wp.Value() == nil {
fmt.Println("Resource was reclaimed")
}
}
10. 性能优化示例
package main
import (
"fmt"
"runtime"
"sync"
"weak"
)
// 使用弱指针的字符串驻留
type StringIntern struct {
mu sync.Mutex
data map[string]weak.Pointer[string]
}
func NewStringIntern() *StringIntern {
return &StringIntern{
data: make(map[string]weak.Pointer[string]),
}
}
func (si *StringIntern) Intern(s string) *string {
si.mu.Lock()
defer si.mu.Unlock()
// 检查是否已存在
if wp, ok := si.data[s]; ok {
if existing := wp.Value(); existing != nil {
return existing
}
// 已被回收
delete(si.data, s)
}
// 存储新字符串
ptr := &s
si.data[s] = weak.Make(ptr)
return ptr
}
func main() {
intern := NewStringIntern()
// 驻留字符串
s1 := intern.Intern("hello")
s2 := intern.Intern("hello")
s3 := intern.Intern("world")
fmt.Printf("s1 == s2: %v\n", s1 == s2) // true
fmt.Printf("s1 == s3: %v\n", s1 == s3) // false
// 内存统计
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
fmt.Printf("Alloc: %d KB\n", mem.Alloc/1024)
}
最佳实践
1. 用于缓存场景
// 推荐:使用弱指针实现缓存
type Cache struct {
data map[string]weak.Pointer[Item]
}
func (c *Cache) Get(key string) *Item {
if wp, ok := c.data[key]; ok {
if item := wp.Value(); item != nil {
return item
}
delete(c.data, key) // 清理已回收的条目
}
return nil
}
2. 及时清理无效引用
// 推荐:定期检查并清理
func (c *Cache) Cleanup() {
for key, wp := range c.data {
if wp.Value() == nil {
delete(c.data, key)
}
}
}
3. 使用 runtime.KeepAlive
// 推荐:确保对象在关键区域存活
func process(wp weak.Pointer[Resource]) {
r := wp.Value()
if r != nil {
// 使用 r
doWork(r)
}
runtime.KeepAlive(r) // 确保 r 在这之前不被回收
}
4. 避免过度使用
// 不推荐:不必要的弱指针
// 如果对象应该一直存在,使用普通指针
// 推荐:仅在需要允许回收时使用
type Cache struct {
data map[string]*Item // 普通指针即可
}
5. 理解比较语义
// 推荐:理解弱指针比较的是对象和偏移
obj := &Struct{A: 1, B: 2}
wpA := weak.Make(&obj.A)
wpB := weak.Make(&obj.B)
fmt.Println(wpA == wpB) // false (不同偏移)
与其他包配合
runtime 包
import (
"runtime"
"weak"
)
// 确保对象存活
func useWeak(wp weak.Pointer[T]) {
v := wp.Value()
if v != nil {
// 使用 v
}
runtime.KeepAlive(v)
}
// 设置终结器
runtime.SetFinalizer(obj, func(o *T) {
// 清理
})
sync 包
import (
"sync"
"weak"
)
// 并发安全的弱指针缓存
type SafeCache struct {
mu sync.RWMutex
data map[string]weak.Pointer[Item]
}
unique 包
import (
"unique"
"weak"
)
// 结合使用 unique 和 weak
type InternMap struct {
data map[unique.Handle[string]]weak.Pointer[string]
}
注意事项
1. Value 不保证最终返回 nil
// 注意:即使对象不再被引用,Value 可能不返回 nil
// 运行时可能将小对象批处理在单个分配槽中
type Tiny struct {
X byte
}
obj := &Tiny{X: 1}
wp := weak.Make(obj)
obj = nil
runtime.GC()
// wp.Value() 可能仍然返回非 nil
// 如果 obj 与其他存活对象在同一批次中
2. 立即返回 nil 的可能性
// 一旦对象不可达,Value 可能立即返回 nil
obj := &Resource{}
wp := weak.Make(obj)
// 即使 obj 仍然在作用域中
// 如果编译器确定 obj 不再使用
// wp.Value() 可能返回 nil
// 使用 runtime.KeepAlive 确保存活
runtime.KeepAlive(obj)
3. Finalizer 的影响
// 如果对象有终结器,Value 在终结器排队时返回 nil
obj := &Resource{}
runtime.SetFinalizer(obj, cleanup)
wp := weak.Make(obj)
obj = nil
// 当终结器被排队时
// wp.Value() 返回 nil
// 即使对象本身还未被回收
4. 比较语义
// 弱指针比较的是对象和偏移,不是地址
type Pair struct {
A, B int
}
pair := &Pair{A: 1, B: 2}
wpA := weak.Make(&pair.A)
wpB := weak.Make(&pair.B)
fmt.Println(wpA == wpB) // false
// 即使 &pair.A 和 &pair.B 可能在同一对象中
5. 复活对象
// 如果对象被终结器复活,弱指针不会比较相等
type Resurrect struct{}
obj := &Resurrect{}
wp1 := weak.Make(obj)
runtime.SetFinalizer(obj, func(o *Resurrect) {
// 复活对象
global = o
})
obj = nil
runtime.GC()
// wp1.Value() 返回 nil
// wp2 := weak.Make(global)
// wp1 != wp2 (即使指向同一对象)
6. 内存优化批处理
// 运行时可能批处理小对象
// 导致弱指针永不变为 nil
type Tiny struct {
X byte // 16 字节或更小,无指针
}
// 这种对象可能被批处理
// 即使不再引用,弱指针也可能保持非 nil
7. 零值行为
// 零值弱指针行为如同通过 nil 创建
var wp weak.Pointer[int]
fmt.Println(wp.Value() == nil) // true
nilWp := weak.Make[int](nil)
fmt.Println(wp == nilWp) // true
8. 泛型支持
// weak.Pointer 是泛型类型
// 可以用于任何类型
intPtr := weak.Make[int](nil)
stringPtr := weak.Make[string](nil)
// 不同类型不兼容
// intPtr == stringPtr // 编译错误
快速参考
类型速查表
| 类型 | 说明 |
|---|---|
Pointer[T] | 指向类型 T 值的弱指针 |
函数速查表
| 函数 | 说明 |
|---|---|
Make[T](ptr) | 从指针创建弱指针 |
Pointer 方法速查表
| 方法 | 说明 |
|---|---|
Value() | 返回原始指针,如果对象被回收则返回 nil |
比较规则
| 情况 | 比较结果 |
|---|---|
| 同一对象的同一偏移 | ✅ 相等 |
| 不同对象 | ❌ 不相等 |
| 同一对象的不同偏移 | ❌ 不相等 |
| 两个 nil 弱指针 | ✅ 相等 |
| 零值弱指针 | 等同于 nil 弱指针 |
常见模式
// 创建弱指针
wp := weak.Make(ptr)
// 检查并使用
if v := wp.Value(); v != nil {
// 使用 v
}
// 清理无效引用
if wp.Value() == nil {
delete(m, key)
}
// 确保对象存活
v := wp.Value()
// 使用 v
runtime.KeepAlive(v)
// 比较弱指针
if wp1 == wp2 {
// 指向同一对象
}
使用场景
| 场景 | 适用性 |
|---|---|
| 缓存 | ✅ 非常适合 |
| 规范化映射 | ✅ 非常适合 |
| 弱键映射 | ✅ 非常适合 |
| 避免循环引用 | ✅ 适合 |
| 观察者模式 | ✅ 适合 |
| 普通指针场景 | ❌ 不必要 |
总结
weak 包提供了安全的弱引用功能:
核心功能:
- 创建不会阻止对象回收的弱指针
- 自动检测对象是否被回收
- 支持泛型,适用于任何类型
主要类型:
Pointer[T]:指向类型 T 的弱指针
主要函数:
Make[T](ptr):从普通指针创建弱指针
使用场景:
- 实现缓存(自动清理)
- 规范化映射(如 unique 包)
- 弱键映射
- 避免循环引用
- 绑定不同值的生命周期
- 观察者模式
使用建议:
- 仅在需要允许回收时使用
- 及时清理无效引用
- 使用 runtime.KeepAlive 确保关键区域对象存活
- 理解比较语义(对象 + 偏移)
- 注意 finalizer 的影响
- 理解 Value 不保证最终返回 nil
典型用法:
// 创建弱指针
wp := weak.Make(obj)
// 检查并使用
if v := wp.Value(); v != nil {
// 对象仍然存活
use(v)
} else {
// 对象已被回收
cleanup()
}
// 在缓存中使用
cache[key] = weak.Make(item)
通过 weak 包,可以实现内存高效的缓存和映射,自动清理不再使用的对象,避免内存泄漏。