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