Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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):从普通指针创建弱指针

使用场景

  1. 实现缓存(自动清理)
  2. 规范化映射(如 unique 包)
  3. 弱键映射
  4. 避免循环引用
  5. 绑定不同值的生命周期
  6. 观察者模式

使用建议

  1. 仅在需要允许回收时使用
  2. 及时清理无效引用
  3. 使用 runtime.KeepAlive 确保关键区域对象存活
  4. 理解比较语义(对象 + 偏移)
  5. 注意 finalizer 的影响
  6. 理解 Value 不保证最终返回 nil

典型用法

// 创建弱指针
wp := weak.Make(obj)

// 检查并使用
if v := wp.Value(); v != nil {
    // 对象仍然存活
    use(v)
} else {
    // 对象已被回收
    cleanup()
}

// 在缓存中使用
cache[key] = weak.Make(item)

通过 weak 包,可以实现内存高效的缓存和映射,自动清理不再使用的对象,避免内存泄漏。