前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Go 1.24: 新的标准库weak

Go 1.24: 新的标准库weak

作者头像
用户11547645
发布2025-03-07 16:01:48
发布2025-03-07 16:01:48
3000
代码可运行
举报
文章被收录于专栏:萝卜要加油萝卜要加油
运行总次数:0
代码可运行

weak 是什么?

Golang 在1.24 中带来了一个新的std-lib weak。 可以为*T 创建一个安全的引用,但是不会阻止*T 被GC 回收。

Package weak provides ways to safely reference memory weakly, that is, without preventing its reclamation.

OS.ROOT一样, weak 也是一个在其他语言中存在很久的功能,比如:

  • JavaWeakReferenceSoftReference 是经典实现,主要用于缓存和对象池。它们能够在 JVM 检测到内存不足时自动回收。
  • Python 提供了weakref 模块,允许创建弱引用对象,常用于防止循环引用问题或缓存。
  • c++std::shared_ptr 中引入了std::weak_ptr,用于解决共享指针的循环依赖问题。
  • Rust 提供RcArc 的弱引用版本Weak,也用于避免循环引用并提升内存管理的灵活性。

weak 的定义很简单,一个Make 方法还有一个Value 方法。

通过weak.Make 创建一个weak.Pointer ,如果T 没有被回收的话,我们可以通过weak.Pointer.Value 获取T的地址。 否则就会返回nil,很简单。 我们可以通过一个简单的例子来实践一下 weak。

代码语言:javascript
代码运行次数:0
复制
funcmain() {
    originalObject := "Hello, World!"
    runtime.AddCleanup(&originalObject, func(sint64) {
        fmt.Println("originalObject clean at: ", s)
    }, time.Now().Unix())
    weakPtr := weak.Make(&originalObject)
    fmt.Println(fmt.Sprintf("originalObject:addr %x", &originalObject))
    fmt.Println(fmt.Sprintf("weakPtr addr:%x,size:%d", weakPtr, unsafe.Sizeof(weakPtr)))
    runtime.GC()
    time.Sleep(1 * time.Millisecond)
    value := weakPtr.Value()
if value != nil && strings.Contains(*value, originalObject) {
        fmt.Println("First GC :value: ", *value)
    } else {
        fmt.Println("first gc. Weak reference value is nil")
    }
    runtime.GC()
    time.Sleep(1 * time.Millisecond)
    value = weakPtr.Value()
if value != nil {
        fmt.Println("Second GC", *value)
    } else {
        fmt.Println("Second GC: Weak reference value is nil")
    }
}

https://gist.github.com/hxzhouh/abd6be9ed8860e506643031bb2d446ce

运行结果

代码语言:javascript
代码运行次数:0
复制
➜  weak git:(main) ✗ gotip version 
go version devel go1.24-18b5435 Sun Dec 15 21:41:28 2024 -0800 darwin/arm64
➜  weak git:(main) ✗ gotip run main.go              
originalObject:addr 14000010050
weakPtr addr:{1400000e0d0},size:8
First GC :value:  Hello, World!
originalObject clean at:  1734340907
Second GC: Weak reference value is nil

在上面的代码中,我们创建了一个string 变量originalObject ,然后使用weak.Make 创建了一个weak.Pointer weakPtr

  • 在第一次GC 的时候,因为originalObject 在后面还有使用,所以weakPtr.Value 返回了originalObject 的地址。
  • 在第二次GC 的时候,originalObject 没有被使用,它被GC回收了, 所以weakPtr.Value 返回了nil
  • runtime.AddCleanup 也是go 1.24 新增的功能,它的功能类似runtime.SetFinalizer,也是在 对象被垃圾回收的时候用于执行一段代码。我后面可能会详细介绍它 通过上面的例子,我们可以知道
  1. weak.Make 通过创建一个中间地址(weak.Printer)将真实地址隐藏起来。
  2. weak.Printer 不会影响 真实地址的垃圾回收,如果真实地址被垃圾回收了,weak.Printer.Value 将会返回nil。由于不知道真实地址什么时候会被回收,所以需要仔细检查weak.Printer.Value 的返回值。

weak 有什么作用

canonicalization maps

相信您还记得在go 1.23 中添加的unique 它可以将多个相同的字符串用一个指针(8个字节)来表示,达到节约内存的目的 ,weak 也能实现类似的效果.(实际上go 1.24unique 已经使用 weak 重构了)

实现一个固定大小的缓存

下面是一个使用weak+list.List实现 固定大小缓存的例子

代码语言:javascript
代码运行次数:0
复制
type WeakCache struct {  
    cache   map[string]weak.Pointer[list.Element] // Use weak references to store values
    mu      sync.Mutex  
    storage Storage  
}  

// Storage is a fixed-length cache based on doubly linked tables and weaktype Storage struct {
    capacity int// Maximum size of the cache
    list     *list.List  
}
// Set
func(c *WeakCache) Set(key string, value any) {  
// If the element already exists, update the value and move it to the head of the chain table
if elem, exists := c.cache[key]; exists {  
if elemValue := elem.Value(); elemValue != nil {  
          elemValue.Value = &CacheItem{key: key, value: value}  
          c.storage.list.MoveToFront(elemValue)  
          elemWeak := weak.Make(elemValue)  
          c.cache[key] = elemWeak  
return
       } else {  
          c.removeElement(key)  
       }  
    }  
// remove the oldest unused element if capacity is full
if c.storage.list.Len() >= c.storage.capacity {  
       c.evict()  
    }  

// Add new element
    elem := c.storage.list.PushFront(&CacheItem{key: key, value: value})  
    elemWeak := weak.Make(elem)  
    c.cache[key] = elemWeak  
}

完整的代码请参考:https://gist.github.com/hxzhouh/1945d4a1e5a6567f084628d60b63f125 我们可以创建一个固定大小的list ,然后使用一个Map记录keylist 中的位置,value 是一个指向 list.Elementweak.Pointer. 如果 key 存在于list 上,那么Map[key].Value 会返回 list 的地址。 再给cache 添加数据的时候,会先判断list 的大小,如果已经list已经满了的话,就把队尾的数据淘汰。Map[key].Value返回nil。 这样,我们就能构建一个高效+固定大小的cache系统。 weak + 无锁队列 可以构建出更加高效的数据结构。

就我个人而言,weak 在特定场合下还是挺有用处的。使用起来也很简单,我会在项目中积极使用weak

更多关于 weak 的资料

  1. https://tip.golang.org/doc/go1.24#weak
  2. https://github.com/golang/go/issues/67552
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 萝卜要加油 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • weak 是什么?
  • weak 有什么作用
    • canonicalization maps
    • 实现一个固定大小的缓存
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档