> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# go1.24: 新的标准库 weak
- URL: https://huizhou92.com/go1-24-xin-de-biao-zhun-ku-weak/
- Published: 2024-12-14T11:40:46.000Z
- Updated: 2026-09-06T11:39:48.000Z
- Author: huizhou92
- Tags: tech, go, go1_24, #lang-zh-cn

### 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` 方法。  
!["Illustration of  structure, showcasing an efficient data storage and retrieval mechanism as a  alternative in Go programming."](https://images.hxzhouh.com/blog-images/2024/12/6bd1951326d504266e1694b6b5f61f47.png)  
通过`weak.Make` 创建一个`weak.Pointer` ,如果 `T` 没有被回收的话，我们可以通过`weak.Pointer.Value` 获取`T`的地址。 否则就会返回 `nil`，很简单。  
我们可以通过一个简单的例子来实践一下 weak。

```go
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](https://gist.github.com/hxzhouh/abd6be9ed8860e506643031bb2d446ce?ref=huizhou92.com)  
[View code snippet on GitHub Gist →](https://gist.github.com/hxzhouh/abd6be9ed8860e506643031bb2d446ce?ref=huizhou92.com)

运行结果

```shell
➜  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.24` 中`unique` 已经使用 weak 重构了)

#### 实现一个固定大小的缓存

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

```go
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](https://gist.github.com/hxzhouh/1945d4a1e5a6567f084628d60b63f125?ref=huizhou92.com)  
我们可以创建一个固定大小的`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 的资料

1. [https://tip.golang.org/doc/go1.24#weak](https://tip.golang.org/doc/go1.24?ref=huizhou92.com#weak)
2. [https://github.com/golang/go/issues/67552](https://github.com/golang/go/issues/67552?ref=huizhou92.com)