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 也是一个在其他语言中存在很久的功能,比如:
Java 的 WeakReference 和 SoftReference 是经典实现,主要用于缓存和对象池。它们能够在 JVM 检测到内存不足时自动回收。
Python 提供了 weakref 模块,允许创建弱引用对象,常用于防止循环引用问题或缓存。
c++ 在 std::shared_ptr 中引入了 std::weak_ptr,用于解决共享指针的循环依赖问题。
Rust 提供 Rc 和 Arc 的弱引用版本 Weak,也用于避免循环引用并提升内存管理的灵活性。
weak 的定义很简单,一个Make 方法还有一个Value 方法。

通过weak.Make 创建一个weak.Pointer ,如果 T 没有被回收的话,我们可以通过weak.Pointer.Value 获取T的地址。 否则就会返回 nil,很简单。
我们可以通过一个简单的例子来实践一下 weak。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
func main() {
originalObject := "Hello, World!"
runtime.AddCleanup(&originalObject, func(s int64) {
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
运行结果
1
2
3
4
5
6
7
8
|
➜ 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,也是在 对象呗垃圾回收的时候用于执行一段代码。我后面可能会详细介绍它
通过上面的例子,我们可以知道
weak.Make 通过创建一个中间地址(weak.Printer)将真实地址隐藏起来。
weak.Printer 不会影响 真实地址的垃圾回收,如果真实地址被垃圾回收了,weak.Printer.Value 将会返回nil。由于不知道真实地址什么时候会被回收,所以需要仔细检查weak.Printer.Value 的返回值。
Weak 有什么作用
Canonicalization Maps
相信您还记得在go 1.23 中添加的 unique 它可以将多个相同的字符串用一个指针(8个字节)来表示,达到节约内存的目的 , weak 也能实现类似的效果.(实际上 go 1.24 中unique 已经使用 weak 重构了)
实现一个固定大小的缓存
下面是一个使用weak+ list.List 实现 固定大小缓存的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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记录key在list 中的位置,value 是一个指向 list.Element 的weak.Pointer. 如果 key 存在于list 上,那么 Map[key].Value 会返回 list 的地址。 再给cache 添加数据的时候,会先判断list 的大小,如果已经list已经满了的话,就把队尾的数据淘汰。Map[key].Value返回nil。
这样,我们就能构建一个高效+固定大小的cache系统。 weak + 无锁队列 可以构建出更加高效的数据结构。
就我个人而言,weak 在特定场合下还是挺有用处的。使用起来也很简单,我会在项目中积极使用weak
更多关于 weak 的资料
- https://tip.golang.org/doc/go1.24#weak
- https://github.com/golang/go/issues/67552